|
@@ -3,20 +3,249 @@ package parser
|
|
|
import (
|
|
|
"testing"
|
|
|
|
|
|
+ "fmt"
|
|
|
+
|
|
|
+ "code.osinet.fr/fgm/waiig15/ast"
|
|
|
"code.osinet.fr/fgm/waiig15/lexer"
|
|
|
)
|
|
|
|
|
|
-func checkParserErrors(t *testing.T, p *Parser) {
|
|
|
- errors := p.Errors()
|
|
|
- if len(errors) == 0 {
|
|
|
- return
|
|
|
+func TestLetStatements(t *testing.T) {
|
|
|
+ // Try removing the ident, the =, or both, to get human-readable errors.
|
|
|
+ input := `
|
|
|
+let x = 5;
|
|
|
+let y = 10;
|
|
|
+let foobar = 838383;
|
|
|
+`
|
|
|
+ l := lexer.New(input)
|
|
|
+ p := New(l)
|
|
|
+
|
|
|
+ program := p.ParseProgram()
|
|
|
+ checkParserErrors(t, p)
|
|
|
+ if program == nil {
|
|
|
+ t.Fatalf("ParseProgram() returned nil.")
|
|
|
}
|
|
|
|
|
|
- t.Errorf("parser has %d errors", len(errors))
|
|
|
- for _, msg := range errors {
|
|
|
- t.Errorf("parser error: %q", msg)
|
|
|
+ if len(program.Statements) != 3 {
|
|
|
+ t.Fatalf("program.Statements does not contain 3 statements, got=%d",
|
|
|
+ len(program.Statements))
|
|
|
+ }
|
|
|
+
|
|
|
+ tests := []struct {
|
|
|
+ expectedIdentifier string
|
|
|
+ }{
|
|
|
+ {"x"},
|
|
|
+ {"y"},
|
|
|
+ {"foobar"},
|
|
|
+ }
|
|
|
+
|
|
|
+ for i, tt := range tests {
|
|
|
+ stmt := program.Statements[i]
|
|
|
+ if !testLetStatement(t, stmt, tt.expectedIdentifier) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestReturnStatements(t *testing.T) {
|
|
|
+ // Try removing the ident, the =, or both, to get human-readable errors.
|
|
|
+ input := `
|
|
|
+return 5;
|
|
|
+return 10;
|
|
|
+return 993322;
|
|
|
+`
|
|
|
+ l := lexer.New(input)
|
|
|
+ p := New(l)
|
|
|
+
|
|
|
+ program := p.ParseProgram()
|
|
|
+ checkParserErrors(t, p)
|
|
|
+ if program == nil {
|
|
|
+ t.Fatalf("ParseProgram() returned nil.")
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(program.Statements) != 3 {
|
|
|
+ t.Fatalf("program.Statements does not contain 3 statements, got=%d",
|
|
|
+ len(program.Statements))
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, stmt := range program.Statements {
|
|
|
+ // Statement is an interface, we need a concrete type for the value, and
|
|
|
+ // our test input only contains Let statements.
|
|
|
+ returnStmt, ok := stmt.(*ast.ReturnStatement)
|
|
|
+ if !ok {
|
|
|
+ t.Errorf("s not *ast.ReturnStatement{}, got=%T", stmt)
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ if returnStmt.TokenLiteral() != "return" {
|
|
|
+ t.Errorf("s.TokenLiteral not 'return', got=%q",
|
|
|
+ stmt.TokenLiteral())
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestIdentifierExpression(t *testing.T) {
|
|
|
+ const input = "foobar"
|
|
|
+
|
|
|
+ l := lexer.New(input)
|
|
|
+ p := New(l)
|
|
|
+ program := p.ParseProgram()
|
|
|
+ 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].(*ast.ExpressionStatement)
|
|
|
+ if !ok {
|
|
|
+ t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. Got=%T",
|
|
|
+ program.Statements)
|
|
|
+ }
|
|
|
+
|
|
|
+ ident, ok := stmt.Expression.(*ast.Identifier)
|
|
|
+ if !ok {
|
|
|
+ t.Fatalf("exp not *ast.Identifier. got=%T", stmt.Expression)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Why not use input instead of inline strings ?
|
|
|
+ if ident.Value != input {
|
|
|
+ t.Errorf("ident.Value not %s. got=%s", input,
|
|
|
+ ident.Value)
|
|
|
+ }
|
|
|
+ if ident.TokenLiteral() != input {
|
|
|
+ t.Errorf("ident.TokenLiteral not %s. got=%s", input,
|
|
|
+ ident.TokenLiteral())
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestIntegerLiteralExpression(t *testing.T) {
|
|
|
+ input := "5;"
|
|
|
+
|
|
|
+ l := lexer.New(input)
|
|
|
+ p := New(l)
|
|
|
+ program := p.ParseProgram()
|
|
|
+ checkParserErrors(t, p)
|
|
|
+
|
|
|
+ if len(program.Statements) != 1 {
|
|
|
+ t.Fatalf("program does not have 1 statement. got=%d",
|
|
|
+ len(program.Statements))
|
|
|
+ }
|
|
|
+ stmt, ok := program.Statements[0].(*ast.ExpressionStatement)
|
|
|
+ if !ok {
|
|
|
+ t.Fatalf("program.Statements[0] is not *ast.ExpressionStatement. got=%T",
|
|
|
+ program.Statements[0])
|
|
|
+ }
|
|
|
+
|
|
|
+ literal, ok := stmt.Expression.(*ast.IntegerLiteral)
|
|
|
+ if !ok {
|
|
|
+ t.Fatalf("exp not *ast.IntegerLiteral. got=%T", stmt.Expression)
|
|
|
+ }
|
|
|
+ if literal.Value != 5 {
|
|
|
+ t.Errorf("literal.Value not %d. got=%d", 5, literal.Value)
|
|
|
+ }
|
|
|
+ if literal.TokenLiteral() != "5" {
|
|
|
+ t.Errorf("literal.TokenLiteral not %s. got=%s", "5",
|
|
|
+ literal.TokenLiteral())
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestParsingPrefixExpressions(t *testing.T) {
|
|
|
+ prefixTests := []struct {
|
|
|
+ input string
|
|
|
+ operator string
|
|
|
+ integerValue int64
|
|
|
+ }{
|
|
|
+ {"!5", "!", 5},
|
|
|
+ {"-15", "-", 15},
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, tt := range prefixTests {
|
|
|
+ l := lexer.New(tt.input)
|
|
|
+ p := New(l)
|
|
|
+ program := p.ParseProgram()
|
|
|
+ checkParserErrors(t, p)
|
|
|
+
|
|
|
+ if len(program.Statements) != 1 {
|
|
|
+ t.Fatalf("program.Statements does not contain %d statements, got=%d\n",
|
|
|
+ 1, len(program.Statements))
|
|
|
+ }
|
|
|
+
|
|
|
+ stmt, ok := program.Statements[0].(*ast.ExpressionStatement)
|
|
|
+ if !ok {
|
|
|
+ t.Fatalf("program.STatements[0] is not ast.ExpressionStatement. got=%T",
|
|
|
+ program.Statements[0])
|
|
|
+ }
|
|
|
+
|
|
|
+ exp, ok := stmt.Expression.(*ast.PrefixExpression)
|
|
|
+ if !ok {
|
|
|
+ t.Fatalf("stms is not ast.PrefixExpression. got=%T",
|
|
|
+ stmt.Expression)
|
|
|
+ }
|
|
|
+
|
|
|
+ if exp.Operator != tt.operator {
|
|
|
+ t.Fatalf("exp.Operator is not '%s'. got=%s",
|
|
|
+ tt.operator, exp.Operator)
|
|
|
+ }
|
|
|
+
|
|
|
+ if !testIntegerLiteral(t, exp.Right, tt.integerValue) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestParsingInfixExpressions(t *testing.T) {
|
|
|
+ infixTests := []struct {
|
|
|
+ input string
|
|
|
+ leftValue int64
|
|
|
+ operator string
|
|
|
+ rightValue int64
|
|
|
+ }{
|
|
|
+ {"5 + 5;", 5, "+", 5},
|
|
|
+ {"5 - 5;", 5, "-", 5},
|
|
|
+ {"5 * 5;", 5, "*", 5},
|
|
|
+ {"5 / 5;", 5, "/", 5},
|
|
|
+ {"5 > 5;", 5, ">", 5},
|
|
|
+ {"5 < 5;", 5, "<", 5},
|
|
|
+ {"5 == 5;", 5, "==", 5},
|
|
|
+ {"5 != 5;", 5, "!=", 5},
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, tt := range infixTests {
|
|
|
+ l := lexer.New(tt.input)
|
|
|
+ p := New(l)
|
|
|
+ program := p.ParseProgram()
|
|
|
+ checkParserErrors(t, p)
|
|
|
+
|
|
|
+ if len(program.Statements) != 1 {
|
|
|
+ t.Fatalf("program.Statements does not contain %d statements. got=%d",
|
|
|
+ 1, len(program.Statements))
|
|
|
+ }
|
|
|
+
|
|
|
+ stmt, ok := program.Statements[0].(*ast.ExpressionStatement)
|
|
|
+ if !ok {
|
|
|
+ t.Fatalf("program.Statements[0] is not expressionStatement. got=%T",
|
|
|
+ program.Statements[0])
|
|
|
+ }
|
|
|
+
|
|
|
+ exp, ok := stmt.Expression.(*ast.InfixExpression)
|
|
|
+ if !ok {
|
|
|
+ t.Fatalf("exp is not infixExpression. got=%T", stmt.Expression)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Why no error ?
|
|
|
+ if !testIntegerLiteral(t, exp.Left, tt.leftValue) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if exp.Operator != tt.operator {
|
|
|
+ t.Fatalf("exp.Operator is not '%s'. got=%s", tt.operator,
|
|
|
+ exp.Operator)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Why no error ?
|
|
|
+ if !testIntegerLiteral(t, exp.Right, tt.rightValue) {
|
|
|
+ return
|
|
|
+ }
|
|
|
}
|
|
|
- t.FailNow()
|
|
|
}
|
|
|
|
|
|
func TestOperatorPrecedenceParsing(t *testing.T) {
|
|
@@ -86,3 +315,132 @@ func TestOperatorPrecedenceParsing(t *testing.T) {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+func testLetStatement(t *testing.T, s ast.Statement, name string) bool {
|
|
|
+ if s.TokenLiteral() != "let" {
|
|
|
+ t.Errorf("s.TokenLiteral not 'let'. got=%q", s.TokenLiteral())
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ // Statement is an interface, we need a concrete type for the value, and we
|
|
|
+ // just determined this looked like a LetStatement.
|
|
|
+ letStmt, ok := s.(*ast.LetStatement)
|
|
|
+ if !ok {
|
|
|
+ t.Errorf("s not *ast.LetStatement. got=%T", s)
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ if letStmt.Name.Value != name {
|
|
|
+ t.Errorf("letStmt.Name.Value not '%s'. got=%s", name, letStmt.Name.Value)
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ if letStmt.Name.TokenLiteral() != name {
|
|
|
+ t.Errorf("letStmt.Name.TokenLiteral() not '%s'. got=%s",
|
|
|
+ name, letStmt.Name.TokenLiteral())
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ return true
|
|
|
+}
|
|
|
+
|
|
|
+func testInfixExpression(
|
|
|
+ t *testing.T,
|
|
|
+ exp ast.Expression,
|
|
|
+ left interface{},
|
|
|
+ operator string,
|
|
|
+ right interface{},
|
|
|
+) bool {
|
|
|
+ opExp, ok := exp.(*ast.InfixExpression)
|
|
|
+ if !ok {
|
|
|
+ t.Errorf("exp is not ast.InfixExpression. got=%T(%s)", exp, exp)
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ if !testLiteralExpression(t, opExp.Left, left) {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ if opExp.Operator != operator {
|
|
|
+ t.Errorf("exp.Operator is not '%s'. got=%q", operator, opExp.Operator)
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ if !testLiteralExpression(t, opExp.Right, right) {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ return true
|
|
|
+}
|
|
|
+
|
|
|
+func testLiteralExpression(
|
|
|
+ t *testing.T,
|
|
|
+ exp ast.Expression,
|
|
|
+ expected interface{},
|
|
|
+) bool {
|
|
|
+ switch v := expected.(type) {
|
|
|
+ case int:
|
|
|
+ return testIntegerLiteral(t, exp, int64(v))
|
|
|
+ case int64:
|
|
|
+ return testIntegerLiteral(t, exp, v)
|
|
|
+ case string:
|
|
|
+ return testIdentifier(t, exp, v)
|
|
|
+ }
|
|
|
+ t.Errorf("type of exp not handled. got=%T", exp)
|
|
|
+ return false
|
|
|
+}
|
|
|
+
|
|
|
+func testIntegerLiteral(t *testing.T, il ast.Expression, value int64) bool {
|
|
|
+ integ, ok := il.(*ast.IntegerLiteral)
|
|
|
+ if !ok {
|
|
|
+ t.Errorf("il not *ast.IntegerLiteral. got=%T", il)
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ if integ.Value != value {
|
|
|
+ t.Errorf("integ.Value not %d. got=%d", value, integ.Value)
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ if integ.TokenLiteral() != fmt.Sprintf("%d", value) {
|
|
|
+ t.Errorf("integ.TokenLiteral not %d. got=%s", value,
|
|
|
+ integ.TokenLiteral())
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ return true
|
|
|
+}
|
|
|
+
|
|
|
+func testIdentifier(t *testing.T, exp ast.Expression, value string) bool {
|
|
|
+ ident, ok := exp.(*ast.Identifier)
|
|
|
+ if !ok {
|
|
|
+ t.Errorf("exp not *ast.Identifier. got=%T", exp)
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ if ident.Value != value {
|
|
|
+ t.Errorf("ident.Value not %s. got=%s", value, ident.Value)
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ if ident.TokenLiteral() != value {
|
|
|
+ t.Errorf("ident.TokenLiteral not %s. got=%s", value,
|
|
|
+ ident.TokenLiteral())
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ return true
|
|
|
+}
|
|
|
+
|
|
|
+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()
|
|
|
+}
|