Skip to content

Commit

Permalink
Result columns (#7)
Browse files Browse the repository at this point in the history
Begin supporting expressions beyond just `*` in select list
  • Loading branch information
chirst authored Nov 26, 2024
1 parent 1731f61 commit e16b9e3
Show file tree
Hide file tree
Showing 10 changed files with 834 additions and 151 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
*.db
*.sqlite
7 changes: 7 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"files.trimTrailingWhitespace": true,
"files.insertFinalNewline": true,
"cSpell.words": [
"chirst"
]
}
101 changes: 69 additions & 32 deletions compiler/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,20 @@ type StmtBase struct {

type SelectStmt struct {
*StmtBase
From *From
ResultColumn ResultColumn
From *From
ResultColumns []ResultColumn
}

// ResultColumn is the column definitions in a select statement.
type ResultColumn struct {
All bool
Count bool
// All is * in a select statement for example SELECT * FROM foo
All bool
// AllTable is all for a table for example SELECT foo.* FROM foo
AllTable string
// Expression contains the more complicated result column rules
Expression Expr
// Alias is the alias for an expression for example SELECT 1 AS "bar"
Alias string
}

type From struct {
Expand All @@ -45,31 +52,61 @@ type InsertStmt struct {
ColValues [][]string
}

// type Expr interface {
// Type() string
// }

// type BinaryExpr struct {
// Left Expr
// Operator string
// Right Expr
// }

// type UnaryExpr struct {
// Operator string
// Operand Expr
// }

// type ColumnRef struct {
// Table string
// Column string
// }

// type IntLit struct {
// Value int
// }

// type FunctionExpr struct {
// Name string
// Args []Expr
// }
// Expr defines the interface of an expression.
type Expr interface {
Type() string // TODO this pattern may not be the best
}

// BinaryExpr is for an expression with two operands.
type BinaryExpr struct {
Left Expr
Operator string
Right Expr
}

func (*BinaryExpr) Type() string { return "BinaryExpr" }

// UnaryExpr is an expression with one operand.
type UnaryExpr struct {
Operator string
Operand Expr
}

func (*UnaryExpr) Type() string { return "UnaryExpr" }

// ColumnRef is an expression with no operands. It references a column on a
// table.
type ColumnRef struct {
Table string
Column string
}

func (*ColumnRef) Type() string { return "ColumnRef" }

// IntLit is an expression that is a literal integer such as "1".
type IntLit struct {
Value int
}

func (*IntLit) Type() string { return "IntLit" }

// StringLit is an expression that is a literal string such as "'asdf'".
type StringLit struct {
Value string
}

func (*StringLit) Type() string { return "StringLit" }

// FunctionExpr is an expression that represents a function.
type FunctionExpr struct {
// FnType corresponds to the type of function. For example fnCount is for
// COUNT(*)
FnType string
Args []Expr
}

const (
FnCount = "COUNT"
)

func (*FunctionExpr) Type() string { return "FunctionExpr" }
65 changes: 52 additions & 13 deletions compiler/lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ const (
tkSeparator
// tkOperator is a symbol that operates on arguments.
tkOperator
// tkPunctuator is punctuation that is neither a separator or operator.
tkPunctuator
// tkLiteral is a quoted text value like 'foo'.
tkLiteral
// tkNumeric is a numeric value like 1, 1.2, or -3.
tkNumeric
// tkPunctuator is punctuation that is neither a separator or operator.
// tkPunctuator
)

// Keywords where kw is keyword
Expand All @@ -55,8 +55,10 @@ const (
kwText = "TEXT"
kwPrimary = "PRIMARY"
kwKey = "KEY"
kwAs = "AS"
)

// keywords is a list of all keywords.
var keywords = []string{
kwExplain,
kwQuery,
Expand All @@ -73,11 +75,35 @@ var keywords = []string{
kwText,
kwPrimary,
kwKey,
kwAs,
}

func (*lexer) isKeyword(w string) bool {
uw := strings.ToUpper(w)
return slices.Contains(keywords, uw)
// Operators where op is operator.
const (
opSub = "-"
opAdd = "+"
opDiv = "/"
opMul = "*"
opExp = "^"
)

// operators is a list of all operators.
var operators = []string{
opSub,
opAdd,
opDiv,
opMul,
opExp,
}

// opPrecedence defines operator precedence. The higher the number the higher
// the precedence.
var opPrecedence = map[string]int{
opSub: 1,
opAdd: 1,
opDiv: 2,
opMul: 2,
opExp: 3,
}

type lexer struct {
Expand Down Expand Up @@ -112,12 +138,12 @@ func (l *lexer) getToken() token {
return l.scanWord()
case l.isDigit(r):
return l.scanDigit()
case l.isAsterisk(r):
return l.scanAsterisk()
case l.isSeparator(r):
return l.scanSeparator()
case l.isSingleQuote(r):
return l.scanLiteral()
case l.isOperator(r):
return l.scanOperator()
}
return token{tkEOF, ""}
}
Expand Down Expand Up @@ -164,11 +190,6 @@ func (l *lexer) scanDigit() token {
return token{tokenType: tkNumeric, value: l.src[l.start:l.end]}
}

func (l *lexer) scanAsterisk() token {
l.next()
return token{tokenType: tkPunctuator, value: l.src[l.start:l.end]}
}

func (l *lexer) scanSeparator() token {
l.next()
return token{tokenType: tkSeparator, value: l.src[l.start:l.end]}
Expand All @@ -183,6 +204,11 @@ func (l *lexer) scanLiteral() token {
return token{tokenType: tkLiteral, value: l.src[l.start:l.end]}
}

func (l *lexer) scanOperator() token {
l.next()
return token{tokenType: tkOperator, value: l.src[l.start:l.end]}
}

func (*lexer) isWhiteSpace(r rune) bool {
return r == ' ' || r == '\t' || r == '\n'
}
Expand All @@ -204,9 +230,22 @@ func (*lexer) isDigit(r rune) bool {
}

func (*lexer) isSeparator(r rune) bool {
return r == ',' || r == '(' || r == ')' || r == ';'
return r == ',' || r == '(' || r == ')' || r == ';' || r == '.'
}

func (*lexer) isSingleQuote(r rune) bool {
return r == '\''
}

func (*lexer) isKeyword(w string) bool {
uw := strings.ToUpper(w)
return slices.Contains(keywords, uw)
}

func (*lexer) isOperator(o rune) bool {
ros := []rune{}
for _, op := range operators {
ros = append(ros, rune(op[0]))
}
return slices.Contains(ros, o)
}
92 changes: 88 additions & 4 deletions compiler/lexer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func TestLexSelect(t *testing.T) {
expected: []token{
{tkKeyword, "SELECT"},
{tkWhitespace, " "},
{tkPunctuator, "*"},
{tkOperator, "*"},
{tkWhitespace, " "},
{tkKeyword, "FROM"},
{tkWhitespace, " "},
Expand All @@ -31,7 +31,7 @@ func TestLexSelect(t *testing.T) {
{tkWhitespace, " "},
{tkKeyword, "COUNT"},
{tkSeparator, "("},
{tkPunctuator, "*"},
{tkOperator, "*"},
{tkSeparator, ")"},
{tkWhitespace, " "},
{tkKeyword, "FROM"},
Expand All @@ -44,7 +44,7 @@ func TestLexSelect(t *testing.T) {
expected: []token{
{tkKeyword, "SELECT"},
{tkWhitespace, " "},
{tkPunctuator, "*"},
{tkOperator, "*"},
{tkWhitespace, " "},
{tkKeyword, "FROM"},
{tkWhitespace, " "},
Expand All @@ -59,7 +59,7 @@ func TestLexSelect(t *testing.T) {
expected: []token{
{tkKeyword, "SELECT"},
{tkWhitespace, " "},
{tkPunctuator, "*"},
{tkOperator, "*"},
{tkWhitespace, " "},
{tkKeyword, "FROM"},
{tkWhitespace, " "},
Expand Down Expand Up @@ -107,6 +107,90 @@ func TestLexSelect(t *testing.T) {
{tkSeparator, ";"},
},
},
{
sql: "SELECT foo.id FROM foo",
expected: []token{
{tkKeyword, "SELECT"},
{tkWhitespace, " "},
{tkIdentifier, "foo"},
{tkSeparator, "."},
{tkIdentifier, "id"},
{tkWhitespace, " "},
{tkKeyword, "FROM"},
{tkWhitespace, " "},
{tkIdentifier, "foo"},
},
},
{
sql: "SELECT foo.* FROM foo",
expected: []token{
{tkKeyword, "SELECT"},
{tkWhitespace, " "},
{tkIdentifier, "foo"},
{tkSeparator, "."},
{tkOperator, "*"},
{tkWhitespace, " "},
{tkKeyword, "FROM"},
{tkWhitespace, " "},
{tkIdentifier, "foo"},
},
},
{
sql: "SELECT 1 AS bar FROM foo",
expected: []token{
{tkKeyword, "SELECT"},
{tkWhitespace, " "},
{tkNumeric, "1"},
{tkWhitespace, " "},
{tkKeyword, "AS"},
{tkWhitespace, " "},
{tkIdentifier, "bar"},
{tkWhitespace, " "},
{tkKeyword, "FROM"},
{tkWhitespace, " "},
{tkIdentifier, "foo"},
},
},
{
sql: "SELECT 1 + 2 - 3 * 4 + 5 / 6 ^ 7 - 8 * 9",
expected: []token{
{tkKeyword, "SELECT"},
{tkWhitespace, " "},
{tkNumeric, "1"},
{tkWhitespace, " "},
{tkOperator, "+"},
{tkWhitespace, " "},
{tkNumeric, "2"},
{tkWhitespace, " "},
{tkOperator, "-"},
{tkWhitespace, " "},
{tkNumeric, "3"},
{tkWhitespace, " "},
{tkOperator, "*"},
{tkWhitespace, " "},
{tkNumeric, "4"},
{tkWhitespace, " "},
{tkOperator, "+"},
{tkWhitespace, " "},
{tkNumeric, "5"},
{tkWhitespace, " "},
{tkOperator, "/"},
{tkWhitespace, " "},
{tkNumeric, "6"},
{tkWhitespace, " "},
{tkOperator, "^"},
{tkWhitespace, " "},
{tkNumeric, "7"},
{tkWhitespace, " "},
{tkOperator, "-"},
{tkWhitespace, " "},
{tkNumeric, "8"},
{tkWhitespace, " "},
{tkOperator, "*"},
{tkWhitespace, " "},
{tkNumeric, "9"},
},
},
}
for _, c := range cases {
t.Run(c.sql, func(t *testing.T) {
Expand Down
Loading

0 comments on commit e16b9e3

Please sign in to comment.