Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
Jamess-Lucass committed May 28, 2024
1 parent f1993a4 commit 15a671b
Show file tree
Hide file tree
Showing 7 changed files with 268 additions and 29 deletions.
57 changes: 57 additions & 0 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ type Statement interface {
statementNode()
}

type Expression interface {
Node
expressionNode()
}

type OrderByDirection string

const (
Expand All @@ -29,3 +34,55 @@ var _ Statement = (*OrderByStatement)(nil)

func (s *OrderByStatement) statementNode() {}
func (s *OrderByStatement) TokenLiteral() string { return s.Token.Literal }

type Identifier struct {
Token token.Token
Value string
}

var _ Expression = (*Identifier)(nil)

func (i *Identifier) expressionNode() {}
func (i *Identifier) TokenLiteral() string { return i.Token.Literal }

type ExpressionStatement struct {
Token token.Token
Expression InfixExpression
}

var _ Statement = (*ExpressionStatement)(nil)

func (s *ExpressionStatement) statementNode() {}
func (s *ExpressionStatement) TokenLiteral() string { return s.Token.Literal }

type InfixExpression struct {
Token token.Token
Left Expression
Operator string
Right Expression
}

var _ Expression = (*InfixExpression)(nil)

func (s *InfixExpression) expressionNode() {}
func (s *InfixExpression) TokenLiteral() string { return s.Token.Literal }

type StringLiteral struct {
Token token.Token
Value string
}

var _ Expression = (*StringLiteral)(nil)

func (s *StringLiteral) expressionNode() {}
func (s *StringLiteral) TokenLiteral() string { return s.Token.Literal }

type IntegerLiteral struct {
Token token.Token
Value int64
}

var _ Expression = (*IntegerLiteral)(nil)

func (s *IntegerLiteral) expressionNode() {}
func (s *IntegerLiteral) TokenLiteral() string { return s.Token.Literal }
9 changes: 9 additions & 0 deletions keywords/keyword.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package keyword

const (
ASC = "ASC"
DESC = "DESC"
EQ = "EQ"
AND = "AND"
OR = "OR"
)
7 changes: 5 additions & 2 deletions lexer/lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,13 @@ func (l *Lexer) NextToken() token.Token {
case 0:
tok.Literal = ""
tok.Type = token.EOF
case '\'':
tok.Type = token.STRING
tok.Literal = l.readString()
default:
if isLetter(l.character) {
tok.Literal = l.readIdentifier()
tok.Type = token.LookupIdent(tok.Literal)
tok.Type = token.IDENT
return tok
} else if isDigit(l.character) {
tok.Literal = l.readNumber()
Expand Down Expand Up @@ -84,7 +87,7 @@ func (l *Lexer) readString() string {

for {
l.readCharacter()
if l.character == '"' || l.character == 0 {
if l.character == '\'' || l.character == 0 {
break
}
}
Expand Down
52 changes: 47 additions & 5 deletions lexer/lexer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,61 @@ func Test_OrderByNextToken(t *testing.T) {
expectedLiteral string
}{
{token.IDENT, "id"},
{token.ASC, "asc"},
{token.IDENT, "asc"},

{token.IDENT, "iD"},
{token.DESC, "desc"},
{token.IDENT, "desc"},

{token.IDENT, "id"},
{token.ASC, "aSc"},
{token.IDENT, "aSc"},

{token.IDENT, "id"},
{token.DESC, "DeSc"},
{token.IDENT, "DeSc"},

{token.IDENT, "id"},
{token.ASC, "AsC"},
{token.IDENT, "AsC"},
}

lexer := NewLexer(input)

for _, test := range tests {
token := lexer.NextToken()

assert.Equal(t, test.expectedType, token.Type)
assert.Equal(t, test.expectedLiteral, token.Literal)
}
}

func Test_FilterNextToken(t *testing.T) {
input := `Name eq 'John'
Id eq 1
Name eq 'John' and Id eq 1
eq eq 'John'
`

tests := []struct {
expectedType token.TokenType
expectedLiteral string
}{
{token.IDENT, "Name"},
{token.IDENT, "eq"},
{token.STRING, "John"},

{token.IDENT, "Id"},
{token.IDENT, "eq"},
{token.INT, "1"},

{token.IDENT, "Name"},
{token.IDENT, "eq"},
{token.STRING, "John"},
{token.IDENT, "and"},
{token.IDENT, "Id"},
{token.IDENT, "eq"},
{token.INT, "1"},

{token.IDENT, "eq"},
{token.IDENT, "eq"},
{token.STRING, "John"},
}

lexer := NewLexer(input)
Expand Down
80 changes: 77 additions & 3 deletions parser/parser.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package parser

import (
"strconv"
"strings"

"github.com/goatquery/goatquery-go/ast"
keyword "github.com/goatquery/goatquery-go/keywords"
"github.com/goatquery/goatquery-go/lexer"
"github.com/goatquery/goatquery-go/token"
)
Expand Down Expand Up @@ -38,12 +42,12 @@ func (p *Parser) ParseOrderBy() []ast.OrderByStatement {

statement := &ast.OrderByStatement{Token: p.currentToken, Direction: ast.Ascending}

if p.peekTokenIs(token.DESC) {
if p.peekIdentiferIs(keyword.DESC) {
statement.Direction = ast.Descending

p.NextToken()
}

p.NextToken()

statements = append(statements, *statement)

p.NextToken()
Expand All @@ -52,10 +56,80 @@ func (p *Parser) ParseOrderBy() []ast.OrderByStatement {
return statements
}

func (p *Parser) ParseFilter() *ast.ExpressionStatement {
statement := &ast.ExpressionStatement{Token: p.currentToken}
statement.Expression = p.ParseExpression()

return statement
}

func (p *Parser) ParseExpression() ast.InfixExpression {
left := p.ParseFilterStatement()

p.NextToken()

for p.currentIdentiferIs(keyword.AND) || p.currentIdentiferIs(keyword.OR) {
left = &ast.InfixExpression{Token: p.currentToken, Left: left, Operator: p.currentToken.Literal}

p.NextToken()

right := p.ParseFilterStatement()
left.Right = right

p.NextToken()
}

return *left
}

func (p *Parser) ParseFilterStatement() *ast.InfixExpression {
identifer := ast.Identifier{Token: p.currentToken, Value: p.currentToken.Literal}

if !p.peekIdentiferIs(keyword.EQ) {
return nil
}

p.NextToken()

statement := ast.InfixExpression{Token: p.currentToken, Left: &identifer, Operator: p.currentToken.Literal}

if !p.peekTokenIs(token.STRING) && !p.peekTokenIs(token.INT) {
return nil
}

p.NextToken()

switch p.currentToken.Type {
case token.STRING:
statement.Right = &ast.StringLiteral{Token: p.currentToken, Value: p.currentToken.Literal}
case token.INT:
literal := &ast.IntegerLiteral{Token: p.currentToken}

value, err := strconv.ParseInt(p.currentToken.Literal, 0, 64)
if err != nil {
return nil
}

literal.Value = value

statement.Right = literal
}

return &statement
}

func (p *Parser) currentTokenIs(t token.TokenType) bool {
return p.currentToken.Type == t
}

func (p *Parser) peekTokenIs(t token.TokenType) bool {
return p.peekToken.Type == t
}

func (p *Parser) peekIdentiferIs(identifier string) bool {
return p.peekToken.Type == token.IDENT && strings.EqualFold(p.peekToken.Literal, identifier)
}

func (p *Parser) currentIdentiferIs(identifier string) bool {
return p.currentToken.Type == token.IDENT && strings.EqualFold(p.currentToken.Literal, identifier)
}
73 changes: 73 additions & 0 deletions parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,76 @@ func Test_ParsingOrderByStatement(t *testing.T) {
assert.Equal(t, test.expectedDirection, stmt.Direction)
}
}

func Test_ParsingFilterStatement(t *testing.T) {
tests := []struct {
input string
expectedLeft string
expectedOperator string
expectedRight string
}{
{"Name eq 'John'", "Name", "eq", "John"},
{"Firstname eq 'Jane'", "Firstname", "eq", "Jane"},
{"Age eq 21", "Age", "eq", "21"},
}

for _, test := range tests {
l := lexer.NewLexer(test.input)
p := NewParser(l)

statement := p.ParseFilter()

expression := statement.Expression
assert.NotNil(t, expression)

assert.Equal(t, test.expectedLeft, expression.Left.TokenLiteral())
assert.Equal(t, test.expectedOperator, expression.Operator)
assert.Equal(t, test.expectedRight, expression.Right.TokenLiteral())
}
}

func Test_ParsingComplexFilterStatement(t *testing.T) {
input := `Name eq 'John' and Age eq 10 or Id eq 10`

l := lexer.NewLexer(input)
p := NewParser(l)

statement := p.ParseFilter()

expression := statement.Expression
assert.NotNil(t, expression)

//Left
left, ok := expression.Left.(*ast.InfixExpression)
assert.True(t, ok)

// Inner left
innerLeft, ok := left.Left.(*ast.InfixExpression)
assert.True(t, ok)

assert.Equal(t, "Name", innerLeft.Left.TokenLiteral())
assert.Equal(t, "eq", innerLeft.Operator)
assert.Equal(t, "John", innerLeft.Right.TokenLiteral())

// inner operator
assert.Equal(t, "and", left.Operator)

// inner right
innerRight, ok := left.Right.(*ast.InfixExpression)
assert.True(t, ok)

assert.Equal(t, "Age", innerRight.Left.TokenLiteral())
assert.Equal(t, "eq", innerRight.Operator)
assert.Equal(t, "10", innerRight.Right.TokenLiteral())

// operator
assert.Equal(t, "or", expression.Operator)

// right
right, ok := expression.Right.(*ast.InfixExpression)
assert.True(t, ok)

assert.Equal(t, "Id", right.Left.TokenLiteral())
assert.Equal(t, "eq", right.Operator)
assert.Equal(t, "10", right.Right.TokenLiteral())
}
19 changes: 0 additions & 19 deletions token/token.go
Original file line number Diff line number Diff line change
@@ -1,33 +1,14 @@
package token

import "strings"

const (
ILLEGAL = "ILLEGAL"
EOF = "EOF"

IDENT = "IDENT"
INT = "INT"
STRING = "STRING"

// Keywords
ASC = "ASC"
DESC = "DESC"
)

var keywords = map[string]TokenType{
"asc": ASC,
"desc": DESC,
}

func LookupIdent(identifier string) TokenType {
if tok, ok := keywords[strings.ToLower(identifier)]; ok {
return tok
}

return IDENT
}

type TokenType string

type Token struct {
Expand Down

0 comments on commit 15a671b

Please sign in to comment.