go-pkg/rsql/parser_test.go
2024-04-05 13:26:11 -06:00

310 lines
7.2 KiB
Go

package rsql
import (
"fmt"
"strings"
"testing"
"github.com/matryer/is"
)
func typeOf(t any) string { return fmt.Sprintf("%T", t) }
func TestIdentifierExpression(t *testing.T) {
input := `foobar`
t.Run("Identifier Expressions", func(t *testing.T) {
is := is.New(t)
l := NewLexer(input)
p := NewParser(l)
program := p.ParseProgram(nil)
checkParserErrors(t, p)
is.Equal(len(program.Statements), 1)
// if len(program.Statements) != 1 {
// t.Fatalf("program has not envough statements. got=%d", len(program.Statements))
// }
})
}
func TestIntegerExpression(t *testing.T) {
input := `5`
t.Run("IntegerExpression", func(t *testing.T) {
is := is.New(t)
l := NewLexer(input)
p := NewParser(l)
program := p.ParseProgram(nil)
checkParserErrors(t, p)
is.Equal(len(program.Statements), 1)
// if len(program.Statements) != 1 {
// t.Fatalf("program has not enough statements. got=%d", len(program.Statements))
// }
stmt, ok := program.Statements[0].(*ExpressionStatement)
is.Equal(typeOf(program.Statements[0]), typeOf(&ExpressionStatement{}))
is.True(ok)
// if !ok {
// t.Fatalf("program.Statements[0] is not ExpressionStatement got=%T",
// program.Statements[0])
// }
literal, ok := stmt.Expression.(*Integer)
is.Equal(typeOf(literal), typeOf(&Integer{}))
is.True(ok)
// if !ok {
// t.Fatalf("stmt.Expression is not Integer got=%T",
// stmt.Expression)
// }
is.Equal(literal.Value, int64(5))
// if literal.Value != 5 {
// t.Errorf("literal.Value not %d. got=%d", 5, literal.Value)
// }
is.Equal(literal.TokenLiteral(), "5")
// if literal.TokenLiteral() != "5" {
// t.Errorf("literal.TokenLiteral not %v. got=%v", "5", literal.TokenLiteral())
// }
})
}
func TestInfixExpression(t *testing.T) {
tests := []struct {
input string
left string
operator string
right int64
}{
{"foo == 1", "foo", "==", 1},
{"bar > 2", "bar", ">", 2},
{"bin < 3", "bin", "<", 3},
{"baz != 4", "baz", "!=", 4},
{"buf >= 5", "buf", ">=", 5},
{"goz <= 6", "goz", "<=", 6},
}
t.Run("Infix Expressions", func(t *testing.T) {
is := is.New(t)
for _, tt := range tests {
l := NewLexer(tt.input)
p := NewParser(l)
program := p.ParseProgram(nil)
checkParserErrors(t, p)
is.Equal(len(program.Statements), 1)
// if len(program.Statements) != 1 {
// t.Fatalf("program has not envough statements. got=%d", len(program.Statements))
// }
stmt, ok := program.Statements[0].(*ExpressionStatement)
is.Equal(typeOf(stmt), typeOf(&ExpressionStatement{}))
is.True(ok)
// if !ok {
// t.Fatalf("program.Statements[0] is not ExpressionStatement got=%T",
// program.Statements[0])
// }
exp, ok := stmt.Expression.(*InfixExpression)
is.Equal(typeOf(exp), typeOf(&InfixExpression{}))
is.True(ok)
// if !ok {
// t.Fatalf("stmt.Expression is not InfixExpression got=%T",
// stmt.Expression)
// }
if !testIdentifier(t, exp.Left, tt.left) {
return
}
is.Equal(exp.Operator, tt.operator)
// if exp.Operator != tt.operator {
// t.Fatalf("exp.Operator is not '%v'. got '%v'", tt.operator, exp.Operator)
// }
if testInteger(t, exp.Right, tt.right) {
return
}
}
})
}
func TestOperatorPrecedenceParsing(t *testing.T) {
tests := []struct {
input string
expect string
}{
{
"foo == 1; bar == 2.0",
"((foo==1);(bar==2.0))",
},
{
`director=='name\'s';actor=eq="name\'s";Year=le=2000,Year>=2010;one <= -1.0, two != true`,
`((((director=="name's");(actor=eq="name's"));((Year=le=2000),(Year>=2010)));((one<=-1.0),(two!=true)))`,
},
}
t.Run("Operator Precidence Parsing", func(t *testing.T) {
is := is.New(t)
for _, tt := range tests {
l := NewLexer(tt.input)
p := NewParser(l)
program := p.ParseProgram(nil)
checkParserErrors(t, p)
actual := program.String()
is.Equal(actual, tt.expect)
// if actual != tt.expect {
// t.Errorf("expcected=%q, got=%q", tt.expect, actual)
// }
}
})
}
func TestParsingArray(t *testing.T) {
input := "[1, 2.1, true, null]"
l := NewLexer(input)
p := NewParser(l)
program := p.ParseProgram(nil)
checkParserErrors(t, p)
if len(program.Statements) != 1 {
t.Fatalf("program has not enough statements. got=%d", len(program.Statements))
}
stmt, ok := program.Statements[0].(*ExpressionStatement)
if !ok {
t.Fatalf("program.Statements[0] is not ExpressionStatement got=%T",
program.Statements[0])
}
array, ok := stmt.Expression.(*Array)
if !ok {
t.Fatalf("stmt.Expression is not Array got=%T",
stmt.Expression)
}
if len(array.Elements) != 4 {
t.Fatalf("len(array.Elements) not 4. got=%v", len(array.Elements))
}
testInteger(t, array.Elements[0], 1)
testFloat(t, array.Elements[1], 2.1)
testBool(t, array.Elements[2], true)
testNull(t, array.Elements[3])
}
func checkParserErrors(t *testing.T, p *Parser) {
errors := p.Errors()
if len(errors) == 0 {
return
}
t.Errorf("parser has %d errors", len(errors))
for _, msg := range errors {
t.Errorf("parser error: %q", msg)
}
t.FailNow()
}
func testInteger(t *testing.T, e Expression, value int64) bool {
literal, ok := e.(*Integer)
if !ok {
t.Errorf("stmt.Expression is not Integer got=%T", e)
return false
}
if literal.Value != value {
t.Errorf("literal.Value not %d. got=%d", value, literal.Value)
return false
}
if literal.TokenLiteral() != fmt.Sprintf("%v", value) {
t.Errorf("literal.TokenLiteral not %v. got=%v", value, literal.TokenLiteral())
return false
}
return true
}
func testFloat(t *testing.T, e Expression, value float64) bool {
literal, ok := e.(*Float)
if !ok {
t.Errorf("stmt.Expression is not Float got=%T", e)
return false
}
if literal.Value != value {
t.Errorf("literal.Value not %f. got=%f", value, literal.Value)
return false
}
if literal.TokenLiteral() != fmt.Sprintf("%v", value) {
t.Errorf("literal.TokenLiteral not %q. got=%q", fmt.Sprintf("%v", value), literal.TokenLiteral())
return false
}
return true
}
func testBool(t *testing.T, e Expression, value bool) bool {
literal, ok := e.(*Bool)
if !ok {
t.Errorf("stmt.Expression is not Float got=%T", e)
return false
}
if literal.Value != value {
t.Errorf("literal.Value not %t. got=%t", value, literal.Value)
return false
}
if literal.TokenLiteral() != fmt.Sprintf("%v", value) {
t.Errorf("literal.TokenLiteral not %v. got=%v", value, literal.TokenLiteral())
return false
}
return true
}
func testNull(t *testing.T, e Expression) bool {
literal, ok := e.(*Null)
if !ok {
t.Errorf("stmt.Expression is not Null got=%T", e)
return false
}
if literal.Token.Type != TokNULL {
t.Errorf("liternal.Token is not TokNULL got=%T", e)
return false
}
return true
}
func testIdentifier(t *testing.T, e Expression, value string) bool {
literal, ok := e.(*Identifier)
if !ok {
t.Errorf("stmt.Expression is not Integer got=%T", e)
return false
}
if literal.Value != value {
t.Errorf("literal.Value not %s. got=%s", value, literal.Value)
return false
}
if literal.TokenLiteral() != value {
t.Errorf("literal.TokenLiteral not %v. got=%v", value, literal.TokenLiteral())
return false
}
return true
}
func TestReplace(t *testing.T) {
is := is.New(t)
is.Equal(strings.Replace(`name\'s`, `\'`, `'`, -1), `name's`)
}