| package parser |
| |
| import ( |
| "matheval/ast" |
| "matheval/token" |
| "testing" |
| ) |
| |
| // helper: tokenize inline for concise tests |
| func tokens(toks ...token.Token) []token.Token { |
| return toks |
| } |
| |
| func tok(typ token.Type, lit string, pos int) token.Token { |
| return token.Token{Type: typ, Literal: lit, Pos: pos} |
| } |
| |
| // --- Success cases --- |
| |
| func TestParseSingleNumber(t *testing.T) { |
| toks := tokens( |
| tok(token.Number, "42", 0), |
| tok(token.EOF, "", 2), |
| ) |
| node, err := Parse(toks) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| num, ok := node.(*ast.NumberLit) |
| if !ok { |
| t.Fatalf("expected *ast.NumberLit, got %T", node) |
| } |
| if num.Value != 42 { |
| t.Fatalf("expected 42, got %f", num.Value) |
| } |
| } |
| |
| func TestParseDecimalNumber(t *testing.T) { |
| toks := tokens( |
| tok(token.Number, "3.14", 0), |
| tok(token.EOF, "", 4), |
| ) |
| node, err := Parse(toks) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| num, ok := node.(*ast.NumberLit) |
| if !ok { |
| t.Fatalf("expected *ast.NumberLit, got %T", node) |
| } |
| if num.Value != 3.14 { |
| t.Fatalf("expected 3.14, got %f", num.Value) |
| } |
| } |
| |
| func TestParseAddition(t *testing.T) { |
| // 1 + 2 |
| toks := tokens( |
| tok(token.Number, "1", 0), |
| tok(token.Plus, "+", 2), |
| tok(token.Number, "2", 4), |
| tok(token.EOF, "", 5), |
| ) |
| node, err := Parse(toks) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| expr, ok := node.(*ast.BinaryExpr) |
| if !ok { |
| t.Fatalf("expected *ast.BinaryExpr, got %T", node) |
| } |
| if expr.Op != token.Plus { |
| t.Fatalf("expected Plus, got %v", expr.Op) |
| } |
| assertNumber(t, expr.Left, 1) |
| assertNumber(t, expr.Right, 2) |
| } |
| |
| func TestParseSubtraction(t *testing.T) { |
| // 5 - 3 |
| toks := tokens( |
| tok(token.Number, "5", 0), |
| tok(token.Minus, "-", 2), |
| tok(token.Number, "3", 4), |
| tok(token.EOF, "", 5), |
| ) |
| node, err := Parse(toks) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| expr, ok := node.(*ast.BinaryExpr) |
| if !ok { |
| t.Fatalf("expected *ast.BinaryExpr, got %T", node) |
| } |
| if expr.Op != token.Minus { |
| t.Fatalf("expected Minus, got %v", expr.Op) |
| } |
| assertNumber(t, expr.Left, 5) |
| assertNumber(t, expr.Right, 3) |
| } |
| |
| func TestParseMultiplication(t *testing.T) { |
| // 2 * 3 |
| toks := tokens( |
| tok(token.Number, "2", 0), |
| tok(token.Star, "*", 2), |
| tok(token.Number, "3", 4), |
| tok(token.EOF, "", 5), |
| ) |
| node, err := Parse(toks) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| expr, ok := node.(*ast.BinaryExpr) |
| if !ok { |
| t.Fatalf("expected *ast.BinaryExpr, got %T", node) |
| } |
| if expr.Op != token.Star { |
| t.Fatalf("expected Star, got %v", expr.Op) |
| } |
| assertNumber(t, expr.Left, 2) |
| assertNumber(t, expr.Right, 3) |
| } |
| |
| func TestParseDivision(t *testing.T) { |
| // 6 / 2 |
| toks := tokens( |
| tok(token.Number, "6", 0), |
| tok(token.Slash, "/", 2), |
| tok(token.Number, "2", 4), |
| tok(token.EOF, "", 5), |
| ) |
| node, err := Parse(toks) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| expr, ok := node.(*ast.BinaryExpr) |
| if !ok { |
| t.Fatalf("expected *ast.BinaryExpr, got %T", node) |
| } |
| if expr.Op != token.Slash { |
| t.Fatalf("expected Slash, got %v", expr.Op) |
| } |
| assertNumber(t, expr.Left, 6) |
| assertNumber(t, expr.Right, 2) |
| } |
| |
| func TestParsePrecedence(t *testing.T) { |
| // 1 + 2 * 3 → 1 + (2 * 3) |
| toks := tokens( |
| tok(token.Number, "1", 0), |
| tok(token.Plus, "+", 2), |
| tok(token.Number, "2", 4), |
| tok(token.Star, "*", 6), |
| tok(token.Number, "3", 8), |
| tok(token.EOF, "", 9), |
| ) |
| node, err := Parse(toks) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| // Root should be Plus |
| expr, ok := node.(*ast.BinaryExpr) |
| if !ok { |
| t.Fatalf("expected *ast.BinaryExpr, got %T", node) |
| } |
| if expr.Op != token.Plus { |
| t.Fatalf("expected Plus at root, got %v", expr.Op) |
| } |
| assertNumber(t, expr.Left, 1) |
| // Right should be Star |
| right, ok := expr.Right.(*ast.BinaryExpr) |
| if !ok { |
| t.Fatalf("expected right to be *ast.BinaryExpr, got %T", expr.Right) |
| } |
| if right.Op != token.Star { |
| t.Fatalf("expected Star, got %v", right.Op) |
| } |
| assertNumber(t, right.Left, 2) |
| assertNumber(t, right.Right, 3) |
| } |
| |
| func TestParsePrecedenceMulFirst(t *testing.T) { |
| // 2 * 3 + 1 → (2 * 3) + 1 |
| toks := tokens( |
| tok(token.Number, "2", 0), |
| tok(token.Star, "*", 2), |
| tok(token.Number, "3", 4), |
| tok(token.Plus, "+", 6), |
| tok(token.Number, "1", 8), |
| tok(token.EOF, "", 9), |
| ) |
| node, err := Parse(toks) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| expr, ok := node.(*ast.BinaryExpr) |
| if !ok { |
| t.Fatalf("expected *ast.BinaryExpr, got %T", node) |
| } |
| if expr.Op != token.Plus { |
| t.Fatalf("expected Plus at root, got %v", expr.Op) |
| } |
| left, ok := expr.Left.(*ast.BinaryExpr) |
| if !ok { |
| t.Fatalf("expected left to be *ast.BinaryExpr, got %T", expr.Left) |
| } |
| if left.Op != token.Star { |
| t.Fatalf("expected Star, got %v", left.Op) |
| } |
| assertNumber(t, left.Left, 2) |
| assertNumber(t, left.Right, 3) |
| assertNumber(t, expr.Right, 1) |
| } |
| |
| func TestParseLeftAssociativity(t *testing.T) { |
| // 1 - 2 - 3 → (1 - 2) - 3 |
| toks := tokens( |
| tok(token.Number, "1", 0), |
| tok(token.Minus, "-", 2), |
| tok(token.Number, "2", 4), |
| tok(token.Minus, "-", 6), |
| tok(token.Number, "3", 8), |
| tok(token.EOF, "", 9), |
| ) |
| node, err := Parse(toks) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| // Root: (1 - 2) - 3 |
| expr, ok := node.(*ast.BinaryExpr) |
| if !ok { |
| t.Fatalf("expected *ast.BinaryExpr, got %T", node) |
| } |
| if expr.Op != token.Minus { |
| t.Fatalf("expected Minus at root, got %v", expr.Op) |
| } |
| assertNumber(t, expr.Right, 3) |
| left, ok := expr.Left.(*ast.BinaryExpr) |
| if !ok { |
| t.Fatalf("expected left to be *ast.BinaryExpr, got %T", expr.Left) |
| } |
| if left.Op != token.Minus { |
| t.Fatalf("expected Minus, got %v", left.Op) |
| } |
| assertNumber(t, left.Left, 1) |
| assertNumber(t, left.Right, 2) |
| } |
| |
| func TestParseParentheses(t *testing.T) { |
| // (1 + 2) * 3 |
| toks := tokens( |
| tok(token.LParen, "(", 0), |
| tok(token.Number, "1", 1), |
| tok(token.Plus, "+", 3), |
| tok(token.Number, "2", 5), |
| tok(token.RParen, ")", 6), |
| tok(token.Star, "*", 8), |
| tok(token.Number, "3", 10), |
| tok(token.EOF, "", 11), |
| ) |
| node, err := Parse(toks) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| expr, ok := node.(*ast.BinaryExpr) |
| if !ok { |
| t.Fatalf("expected *ast.BinaryExpr, got %T", node) |
| } |
| if expr.Op != token.Star { |
| t.Fatalf("expected Star at root, got %v", expr.Op) |
| } |
| assertNumber(t, expr.Right, 3) |
| left, ok := expr.Left.(*ast.BinaryExpr) |
| if !ok { |
| t.Fatalf("expected left to be *ast.BinaryExpr, got %T", expr.Left) |
| } |
| if left.Op != token.Plus { |
| t.Fatalf("expected Plus, got %v", left.Op) |
| } |
| assertNumber(t, left.Left, 1) |
| assertNumber(t, left.Right, 2) |
| } |
| |
| func TestParseNestedParentheses(t *testing.T) { |
| // ((1 + 2)) |
| toks := tokens( |
| tok(token.LParen, "(", 0), |
| tok(token.LParen, "(", 1), |
| tok(token.Number, "1", 2), |
| tok(token.Plus, "+", 4), |
| tok(token.Number, "2", 6), |
| tok(token.RParen, ")", 7), |
| tok(token.RParen, ")", 8), |
| tok(token.EOF, "", 9), |
| ) |
| node, err := Parse(toks) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| expr, ok := node.(*ast.BinaryExpr) |
| if !ok { |
| t.Fatalf("expected *ast.BinaryExpr, got %T", node) |
| } |
| if expr.Op != token.Plus { |
| t.Fatalf("expected Plus, got %v", expr.Op) |
| } |
| assertNumber(t, expr.Left, 1) |
| assertNumber(t, expr.Right, 2) |
| } |
| |
| func TestParseComplexExpression(t *testing.T) { |
| // 1 + 2 * 3 - 4 / 2 → (1 + (2*3)) - (4/2) |
| toks := tokens( |
| tok(token.Number, "1", 0), |
| tok(token.Plus, "+", 2), |
| tok(token.Number, "2", 4), |
| tok(token.Star, "*", 5), |
| tok(token.Number, "3", 6), |
| tok(token.Minus, "-", 8), |
| tok(token.Number, "4", 10), |
| tok(token.Slash, "/", 11), |
| tok(token.Number, "2", 12), |
| tok(token.EOF, "", 13), |
| ) |
| node, err := Parse(toks) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| // Root: (1 + (2*3)) - (4/2) |
| root, ok := node.(*ast.BinaryExpr) |
| if !ok { |
| t.Fatalf("expected *ast.BinaryExpr, got %T", node) |
| } |
| if root.Op != token.Minus { |
| t.Fatalf("expected Minus at root, got %v", root.Op) |
| } |
| // Left: 1 + (2*3) |
| left, ok := root.Left.(*ast.BinaryExpr) |
| if !ok { |
| t.Fatalf("expected left to be *ast.BinaryExpr, got %T", root.Left) |
| } |
| if left.Op != token.Plus { |
| t.Fatalf("expected Plus, got %v", left.Op) |
| } |
| assertNumber(t, left.Left, 1) |
| mul, ok := left.Right.(*ast.BinaryExpr) |
| if !ok { |
| t.Fatalf("expected *ast.BinaryExpr, got %T", left.Right) |
| } |
| if mul.Op != token.Star { |
| t.Fatalf("expected Star, got %v", mul.Op) |
| } |
| assertNumber(t, mul.Left, 2) |
| assertNumber(t, mul.Right, 3) |
| // Right: 4/2 |
| div, ok := root.Right.(*ast.BinaryExpr) |
| if !ok { |
| t.Fatalf("expected right to be *ast.BinaryExpr, got %T", root.Right) |
| } |
| if div.Op != token.Slash { |
| t.Fatalf("expected Slash, got %v", div.Op) |
| } |
| assertNumber(t, div.Left, 4) |
| assertNumber(t, div.Right, 2) |
| } |
| |
| // --- Error cases --- |
| |
| func TestParseEmptyInput(t *testing.T) { |
| toks := tokens( |
| tok(token.EOF, "", 0), |
| ) |
| _, err := Parse(toks) |
| if err == nil { |
| t.Fatal("expected error for empty input") |
| } |
| } |
| |
| func TestParseMissingRParen(t *testing.T) { |
| // (1 + 2 |
| toks := tokens( |
| tok(token.LParen, "(", 0), |
| tok(token.Number, "1", 1), |
| tok(token.Plus, "+", 3), |
| tok(token.Number, "2", 5), |
| tok(token.EOF, "", 6), |
| ) |
| _, err := Parse(toks) |
| if err == nil { |
| t.Fatal("expected error for missing right paren") |
| } |
| } |
| |
| func TestParseUnexpectedRParen(t *testing.T) { |
| // ) 1 |
| toks := tokens( |
| tok(token.RParen, ")", 0), |
| tok(token.Number, "1", 2), |
| tok(token.EOF, "", 3), |
| ) |
| _, err := Parse(toks) |
| if err == nil { |
| t.Fatal("expected error for unexpected right paren") |
| } |
| } |
| |
| func TestParseTrailingOperator(t *testing.T) { |
| // 1 + |
| toks := tokens( |
| tok(token.Number, "1", 0), |
| tok(token.Plus, "+", 2), |
| tok(token.EOF, "", 3), |
| ) |
| _, err := Parse(toks) |
| if err == nil { |
| t.Fatal("expected error for trailing operator") |
| } |
| } |
| |
| func TestParseTrailingTokens(t *testing.T) { |
| // 1 2 |
| toks := tokens( |
| tok(token.Number, "1", 0), |
| tok(token.Number, "2", 2), |
| tok(token.EOF, "", 3), |
| ) |
| _, err := Parse(toks) |
| if err == nil { |
| t.Fatal("expected error for trailing tokens") |
| } |
| } |
| |
| func TestParseConsecutiveOperators(t *testing.T) { |
| // 1 + * 2 |
| toks := tokens( |
| tok(token.Number, "1", 0), |
| tok(token.Plus, "+", 2), |
| tok(token.Star, "*", 4), |
| tok(token.Number, "2", 6), |
| tok(token.EOF, "", 7), |
| ) |
| _, err := Parse(toks) |
| if err == nil { |
| t.Fatal("expected error for consecutive operators") |
| } |
| } |
| |
| func TestParseEmptyParens(t *testing.T) { |
| // () |
| toks := tokens( |
| tok(token.LParen, "(", 0), |
| tok(token.RParen, ")", 1), |
| tok(token.EOF, "", 2), |
| ) |
| _, err := Parse(toks) |
| if err == nil { |
| t.Fatal("expected error for empty parentheses") |
| } |
| } |
| |
| // --- Helper --- |
| |
| func assertNumber(t *testing.T, node ast.Node, expected float64) { |
| t.Helper() |
| num, ok := node.(*ast.NumberLit) |
| if !ok { |
| t.Fatalf("expected *ast.NumberLit, got %T", node) |
| } |
| if num.Value != expected { |
| t.Fatalf("expected %f, got %f", expected, num.Value) |
| } |
| } |