| package parser |
| |
| import ( |
| "matheval/ast" |
| "matheval/token" |
| "testing" |
| ) |
| |
| // --- Parse (backward compatibility) --- |
| |
| func TestParse_SingleNumber(t *testing.T) { |
| tokens := []token.Token{ |
| {Type: token.Number, Literal: "42", Pos: 0}, |
| {Type: token.EOF, Literal: "", Pos: 2}, |
| } |
| node, err := Parse(tokens) |
| 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 %v", num.Value) |
| } |
| } |
| |
| func TestParse_BinaryExpr(t *testing.T) { |
| tokens := []token.Token{ |
| {Type: token.Number, Literal: "1", Pos: 0}, |
| {Type: token.Plus, Literal: "+", Pos: 2}, |
| {Type: token.Number, Literal: "2", Pos: 4}, |
| {Type: token.EOF, Literal: "", Pos: 5}, |
| } |
| node, err := Parse(tokens) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| bin, ok := node.(*ast.BinaryExpr) |
| if !ok { |
| t.Fatalf("expected *ast.BinaryExpr, got %T", node) |
| } |
| if bin.Op != token.Plus { |
| t.Fatalf("expected Plus, got %v", bin.Op) |
| } |
| } |
| |
| // --- factor: Ident --- |
| |
| func TestParse_Ident(t *testing.T) { |
| tokens := []token.Token{ |
| {Type: token.Ident, Literal: "x", Pos: 0}, |
| {Type: token.EOF, Literal: "", Pos: 1}, |
| } |
| node, err := Parse(tokens) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| ident, ok := node.(*ast.Ident) |
| if !ok { |
| t.Fatalf("expected *ast.Ident, got %T", node) |
| } |
| if ident.Name != "x" { |
| t.Fatalf("expected 'x', got %q", ident.Name) |
| } |
| } |
| |
| func TestParse_IdentInExpr(t *testing.T) { |
| // x + 1 |
| tokens := []token.Token{ |
| {Type: token.Ident, Literal: "x", Pos: 0}, |
| {Type: token.Plus, Literal: "+", Pos: 2}, |
| {Type: token.Number, Literal: "1", Pos: 4}, |
| {Type: token.EOF, Literal: "", Pos: 5}, |
| } |
| node, err := Parse(tokens) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| bin, ok := node.(*ast.BinaryExpr) |
| if !ok { |
| t.Fatalf("expected *ast.BinaryExpr, got %T", node) |
| } |
| left, ok := bin.Left.(*ast.Ident) |
| if !ok { |
| t.Fatalf("expected left to be *ast.Ident, got %T", bin.Left) |
| } |
| if left.Name != "x" { |
| t.Fatalf("expected 'x', got %q", left.Name) |
| } |
| } |
| |
| // --- factor: FuncCall --- |
| |
| func TestParse_FuncCallNoArgs(t *testing.T) { |
| // f() |
| tokens := []token.Token{ |
| {Type: token.Ident, Literal: "f", Pos: 0}, |
| {Type: token.LParen, Literal: "(", Pos: 1}, |
| {Type: token.RParen, Literal: ")", Pos: 2}, |
| {Type: token.EOF, Literal: "", Pos: 3}, |
| } |
| node, err := Parse(tokens) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| fc, ok := node.(*ast.FuncCall) |
| if !ok { |
| t.Fatalf("expected *ast.FuncCall, got %T", node) |
| } |
| if fc.Name != "f" { |
| t.Fatalf("expected 'f', got %q", fc.Name) |
| } |
| if len(fc.Args) != 0 { |
| t.Fatalf("expected 0 args, got %d", len(fc.Args)) |
| } |
| } |
| |
| func TestParse_FuncCallOneArg(t *testing.T) { |
| // f(42) |
| tokens := []token.Token{ |
| {Type: token.Ident, Literal: "f", Pos: 0}, |
| {Type: token.LParen, Literal: "(", Pos: 1}, |
| {Type: token.Number, Literal: "42", Pos: 2}, |
| {Type: token.RParen, Literal: ")", Pos: 4}, |
| {Type: token.EOF, Literal: "", Pos: 5}, |
| } |
| node, err := Parse(tokens) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| fc, ok := node.(*ast.FuncCall) |
| if !ok { |
| t.Fatalf("expected *ast.FuncCall, got %T", node) |
| } |
| if fc.Name != "f" { |
| t.Fatalf("expected 'f', got %q", fc.Name) |
| } |
| if len(fc.Args) != 1 { |
| t.Fatalf("expected 1 arg, got %d", len(fc.Args)) |
| } |
| arg, ok := fc.Args[0].(*ast.NumberLit) |
| if !ok { |
| t.Fatalf("expected arg to be *ast.NumberLit, got %T", fc.Args[0]) |
| } |
| if arg.Value != 42 { |
| t.Fatalf("expected 42, got %v", arg.Value) |
| } |
| } |
| |
| func TestParse_FuncCallMultiArgs(t *testing.T) { |
| // f(1, 2, 3) |
| tokens := []token.Token{ |
| {Type: token.Ident, Literal: "f", Pos: 0}, |
| {Type: token.LParen, Literal: "(", Pos: 1}, |
| {Type: token.Number, Literal: "1", Pos: 2}, |
| {Type: token.Comma, Literal: ",", Pos: 3}, |
| {Type: token.Number, Literal: "2", Pos: 5}, |
| {Type: token.Comma, Literal: ",", Pos: 6}, |
| {Type: token.Number, Literal: "3", Pos: 8}, |
| {Type: token.RParen, Literal: ")", Pos: 9}, |
| {Type: token.EOF, Literal: "", Pos: 10}, |
| } |
| node, err := Parse(tokens) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| fc, ok := node.(*ast.FuncCall) |
| if !ok { |
| t.Fatalf("expected *ast.FuncCall, got %T", node) |
| } |
| if len(fc.Args) != 3 { |
| t.Fatalf("expected 3 args, got %d", len(fc.Args)) |
| } |
| } |
| |
| func TestParse_FuncCallExprArgs(t *testing.T) { |
| // f(1+2, 3*4) |
| tokens := []token.Token{ |
| {Type: token.Ident, Literal: "f", Pos: 0}, |
| {Type: token.LParen, Literal: "(", Pos: 1}, |
| {Type: token.Number, Literal: "1", Pos: 2}, |
| {Type: token.Plus, Literal: "+", Pos: 3}, |
| {Type: token.Number, Literal: "2", Pos: 4}, |
| {Type: token.Comma, Literal: ",", Pos: 5}, |
| {Type: token.Number, Literal: "3", Pos: 7}, |
| {Type: token.Star, Literal: "*", Pos: 8}, |
| {Type: token.Number, Literal: "4", Pos: 9}, |
| {Type: token.RParen, Literal: ")", Pos: 10}, |
| {Type: token.EOF, Literal: "", Pos: 11}, |
| } |
| node, err := Parse(tokens) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| fc, ok := node.(*ast.FuncCall) |
| if !ok { |
| t.Fatalf("expected *ast.FuncCall, got %T", node) |
| } |
| if len(fc.Args) != 2 { |
| t.Fatalf("expected 2 args, got %d", len(fc.Args)) |
| } |
| // First arg: 1+2 |
| _, ok = fc.Args[0].(*ast.BinaryExpr) |
| if !ok { |
| t.Fatalf("expected first arg to be *ast.BinaryExpr, got %T", fc.Args[0]) |
| } |
| } |
| |
| func TestParse_FuncCallInExpr(t *testing.T) { |
| // f(1) + 2 |
| tokens := []token.Token{ |
| {Type: token.Ident, Literal: "f", Pos: 0}, |
| {Type: token.LParen, Literal: "(", Pos: 1}, |
| {Type: token.Number, Literal: "1", Pos: 2}, |
| {Type: token.RParen, Literal: ")", Pos: 3}, |
| {Type: token.Plus, Literal: "+", Pos: 5}, |
| {Type: token.Number, Literal: "2", Pos: 7}, |
| {Type: token.EOF, Literal: "", Pos: 8}, |
| } |
| node, err := Parse(tokens) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| bin, ok := node.(*ast.BinaryExpr) |
| if !ok { |
| t.Fatalf("expected *ast.BinaryExpr, got %T", node) |
| } |
| _, ok = bin.Left.(*ast.FuncCall) |
| if !ok { |
| t.Fatalf("expected left to be *ast.FuncCall, got %T", bin.Left) |
| } |
| } |
| |
| func TestParse_FuncCallMissingRParen(t *testing.T) { |
| // f(1 |
| tokens := []token.Token{ |
| {Type: token.Ident, Literal: "f", Pos: 0}, |
| {Type: token.LParen, Literal: "(", Pos: 1}, |
| {Type: token.Number, Literal: "1", Pos: 2}, |
| {Type: token.EOF, Literal: "", Pos: 3}, |
| } |
| _, err := Parse(tokens) |
| if err == nil { |
| t.Fatal("expected error for missing closing paren in func call") |
| } |
| } |
| |
| // --- ParseLine: expression statement --- |
| |
| func TestParseLine_ExprStmt(t *testing.T) { |
| // "1 + 2" |
| tokens := []token.Token{ |
| {Type: token.Number, Literal: "1", Pos: 0}, |
| {Type: token.Plus, Literal: "+", Pos: 2}, |
| {Type: token.Number, Literal: "2", Pos: 4}, |
| {Type: token.EOF, Literal: "", Pos: 5}, |
| } |
| stmt, err := ParseLine(tokens) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| es, ok := stmt.(*ast.ExprStmt) |
| if !ok { |
| t.Fatalf("expected *ast.ExprStmt, got %T", stmt) |
| } |
| _, ok = es.Expr.(*ast.BinaryExpr) |
| if !ok { |
| t.Fatalf("expected expr to be *ast.BinaryExpr, got %T", es.Expr) |
| } |
| } |
| |
| func TestParseLine_ExprStmtFuncCall(t *testing.T) { |
| // "f(1)" |
| tokens := []token.Token{ |
| {Type: token.Ident, Literal: "f", Pos: 0}, |
| {Type: token.LParen, Literal: "(", Pos: 1}, |
| {Type: token.Number, Literal: "1", Pos: 2}, |
| {Type: token.RParen, Literal: ")", Pos: 3}, |
| {Type: token.EOF, Literal: "", Pos: 4}, |
| } |
| stmt, err := ParseLine(tokens) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| es, ok := stmt.(*ast.ExprStmt) |
| if !ok { |
| t.Fatalf("expected *ast.ExprStmt, got %T", stmt) |
| } |
| _, ok = es.Expr.(*ast.FuncCall) |
| if !ok { |
| t.Fatalf("expected expr to be *ast.FuncCall, got %T", es.Expr) |
| } |
| } |
| |
| // --- ParseLine: function definition --- |
| |
| func TestParseLine_FuncDefSingleParam(t *testing.T) { |
| // "f(x) = x + 1" |
| tokens := []token.Token{ |
| {Type: token.Ident, Literal: "f", Pos: 0}, |
| {Type: token.LParen, Literal: "(", Pos: 1}, |
| {Type: token.Ident, Literal: "x", Pos: 2}, |
| {Type: token.RParen, Literal: ")", Pos: 3}, |
| {Type: token.Equals, Literal: "=", Pos: 5}, |
| {Type: token.Ident, Literal: "x", Pos: 7}, |
| {Type: token.Plus, Literal: "+", Pos: 9}, |
| {Type: token.Number, Literal: "1", Pos: 11}, |
| {Type: token.EOF, Literal: "", Pos: 12}, |
| } |
| stmt, err := ParseLine(tokens) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| fd, ok := stmt.(*ast.FuncDef) |
| if !ok { |
| t.Fatalf("expected *ast.FuncDef, got %T", stmt) |
| } |
| if fd.Name != "f" { |
| t.Fatalf("expected name 'f', got %q", fd.Name) |
| } |
| if len(fd.Params) != 1 || fd.Params[0] != "x" { |
| t.Fatalf("expected params [x], got %v", fd.Params) |
| } |
| // Body should be x + 1 |
| bin, ok := fd.Body.(*ast.BinaryExpr) |
| if !ok { |
| t.Fatalf("expected body to be *ast.BinaryExpr, got %T", fd.Body) |
| } |
| if bin.Op != token.Plus { |
| t.Fatalf("expected Plus in body, got %v", bin.Op) |
| } |
| } |
| |
| func TestParseLine_FuncDefMultiParam(t *testing.T) { |
| // "add(x, y) = x + y" |
| tokens := []token.Token{ |
| {Type: token.Ident, Literal: "add", Pos: 0}, |
| {Type: token.LParen, Literal: "(", Pos: 3}, |
| {Type: token.Ident, Literal: "x", Pos: 4}, |
| {Type: token.Comma, Literal: ",", Pos: 5}, |
| {Type: token.Ident, Literal: "y", Pos: 7}, |
| {Type: token.RParen, Literal: ")", Pos: 8}, |
| {Type: token.Equals, Literal: "=", Pos: 10}, |
| {Type: token.Ident, Literal: "x", Pos: 12}, |
| {Type: token.Plus, Literal: "+", Pos: 14}, |
| {Type: token.Ident, Literal: "y", Pos: 16}, |
| {Type: token.EOF, Literal: "", Pos: 17}, |
| } |
| stmt, err := ParseLine(tokens) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| fd, ok := stmt.(*ast.FuncDef) |
| if !ok { |
| t.Fatalf("expected *ast.FuncDef, got %T", stmt) |
| } |
| if fd.Name != "add" { |
| t.Fatalf("expected name 'add', got %q", fd.Name) |
| } |
| if len(fd.Params) != 2 || fd.Params[0] != "x" || fd.Params[1] != "y" { |
| t.Fatalf("expected params [x y], got %v", fd.Params) |
| } |
| } |
| |
| func TestParseLine_FuncDefNoParams(t *testing.T) { |
| // "c() = 42" |
| tokens := []token.Token{ |
| {Type: token.Ident, Literal: "c", Pos: 0}, |
| {Type: token.LParen, Literal: "(", Pos: 1}, |
| {Type: token.RParen, Literal: ")", Pos: 2}, |
| {Type: token.Equals, Literal: "=", Pos: 4}, |
| {Type: token.Number, Literal: "42", Pos: 6}, |
| {Type: token.EOF, Literal: "", Pos: 8}, |
| } |
| stmt, err := ParseLine(tokens) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| fd, ok := stmt.(*ast.FuncDef) |
| if !ok { |
| t.Fatalf("expected *ast.FuncDef, got %T", stmt) |
| } |
| if fd.Name != "c" { |
| t.Fatalf("expected name 'c', got %q", fd.Name) |
| } |
| if len(fd.Params) != 0 { |
| t.Fatalf("expected 0 params, got %d", len(fd.Params)) |
| } |
| } |
| |
| // --- ParseLine: error cases --- |
| |
| func TestParseLine_Empty(t *testing.T) { |
| tokens := []token.Token{ |
| {Type: token.EOF, Literal: "", Pos: 0}, |
| } |
| _, err := ParseLine(tokens) |
| if err == nil { |
| t.Fatal("expected error for empty input") |
| } |
| } |
| |
| func TestParseLine_FuncDefMissingBody(t *testing.T) { |
| // "f(x) =" |
| tokens := []token.Token{ |
| {Type: token.Ident, Literal: "f", Pos: 0}, |
| {Type: token.LParen, Literal: "(", Pos: 1}, |
| {Type: token.Ident, Literal: "x", Pos: 2}, |
| {Type: token.RParen, Literal: ")", Pos: 3}, |
| {Type: token.Equals, Literal: "=", Pos: 5}, |
| {Type: token.EOF, Literal: "", Pos: 6}, |
| } |
| _, err := ParseLine(tokens) |
| if err == nil { |
| t.Fatal("expected error for missing function body") |
| } |
| } |
| |
| func TestParseLine_FuncDefBadParams(t *testing.T) { |
| // "f(1) = 2" — params must be identifiers |
| tokens := []token.Token{ |
| {Type: token.Ident, Literal: "f", Pos: 0}, |
| {Type: token.LParen, Literal: "(", Pos: 1}, |
| {Type: token.Number, Literal: "1", Pos: 2}, |
| {Type: token.RParen, Literal: ")", Pos: 3}, |
| {Type: token.Equals, Literal: "=", Pos: 5}, |
| {Type: token.Number, Literal: "2", Pos: 7}, |
| {Type: token.EOF, Literal: "", Pos: 8}, |
| } |
| _, err := ParseLine(tokens) |
| if err == nil { |
| t.Fatal("expected error for numeric parameter in func def") |
| } |
| } |
| |
| func TestParseLine_FuncDefTrailingTokens(t *testing.T) { |
| // "f(x) = x 1" — extra token after body |
| tokens := []token.Token{ |
| {Type: token.Ident, Literal: "f", Pos: 0}, |
| {Type: token.LParen, Literal: "(", Pos: 1}, |
| {Type: token.Ident, Literal: "x", Pos: 2}, |
| {Type: token.RParen, Literal: ")", Pos: 3}, |
| {Type: token.Equals, Literal: "=", Pos: 5}, |
| {Type: token.Ident, Literal: "x", Pos: 7}, |
| {Type: token.Number, Literal: "1", Pos: 9}, |
| {Type: token.EOF, Literal: "", Pos: 10}, |
| } |
| _, err := ParseLine(tokens) |
| if err == nil { |
| t.Fatal("expected error for trailing tokens after function body") |
| } |
| } |