Step 4: Add parser tests for Ident, FuncCall, ParseLine, and error cases
diff --git a/parser/parser.go b/parser/parser.go
index c7be53b..15b4cea 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -23,6 +23,125 @@
return node, nil
}
+// ParseLine parses a full REPL line, returning either a function definition
+// or an expression statement.
+func ParseLine(tokens []token.Token) (ast.Statement, error) {
+ if len(tokens) == 0 || tokens[0].Type == token.EOF {
+ return nil, fmt.Errorf("empty input")
+ }
+
+ // Detect function definition: look for Equals token.
+ // A function definition has the form: Ident LParen params RParen Equals body
+ if isFuncDef(tokens) {
+ return parseFuncDef(tokens)
+ }
+
+ // Otherwise, parse as expression.
+ node, err := Parse(tokens)
+ if err != nil {
+ return nil, err
+ }
+ return &ast.ExprStmt{Expr: node}, nil
+}
+
+// isFuncDef checks if the token stream looks like a function definition.
+// Pattern: Ident LParen ... RParen Equals ...
+func isFuncDef(tokens []token.Token) bool {
+ if len(tokens) < 5 {
+ return false
+ }
+ if tokens[0].Type != token.Ident {
+ return false
+ }
+ if tokens[1].Type != token.LParen {
+ return false
+ }
+ // Find matching RParen, then check for Equals.
+ depth := 0
+ for i := 1; i < len(tokens); i++ {
+ switch tokens[i].Type {
+ case token.LParen:
+ depth++
+ case token.RParen:
+ depth--
+ if depth == 0 {
+ // Next token must be Equals for this to be a func def.
+ if i+1 < len(tokens) && tokens[i+1].Type == token.Equals {
+ return true
+ }
+ return false
+ }
+ case token.EOF:
+ return false
+ }
+ }
+ return false
+}
+
+// parseFuncDef parses: Ident LParen param1, param2, ... RParen Equals body
+func parseFuncDef(tokens []token.Token) (*ast.FuncDef, error) {
+ p := &parser{tokens: tokens}
+
+ // Function name.
+ nameTok, err := p.expect(token.Ident)
+ if err != nil {
+ return nil, fmt.Errorf("expected function name: %w", err)
+ }
+ name := nameTok.Literal
+
+ // Opening paren.
+ if _, err := p.expect(token.LParen); err != nil {
+ return nil, fmt.Errorf("expected '(' after function name: %w", err)
+ }
+
+ // Parameters: comma-separated identifiers.
+ var params []string
+ if p.current().Type != token.RParen {
+ paramTok, err := p.expect(token.Ident)
+ if err != nil {
+ return nil, fmt.Errorf("expected parameter name: %w", err)
+ }
+ params = append(params, paramTok.Literal)
+
+ for p.current().Type == token.Comma {
+ p.advance() // consume comma
+ paramTok, err := p.expect(token.Ident)
+ if err != nil {
+ return nil, fmt.Errorf("expected parameter name after ',': %w", err)
+ }
+ params = append(params, paramTok.Literal)
+ }
+ }
+
+ // Closing paren.
+ if _, err := p.expect(token.RParen); err != nil {
+ return nil, fmt.Errorf("expected ')' after parameters: %w", err)
+ }
+
+ // Equals sign.
+ if _, err := p.expect(token.Equals); err != nil {
+ return nil, fmt.Errorf("expected '=' in function definition: %w", err)
+ }
+
+ // Body expression.
+ body, err := p.expr()
+ if err != nil {
+ return nil, fmt.Errorf("error in function body: %w", err)
+ }
+
+ // Ensure all tokens consumed.
+ if p.current().Type != token.EOF {
+ tok := p.current()
+ return nil, fmt.Errorf("unexpected token %v at position %d after function body", tok.Type, tok.Pos)
+ }
+
+ return &ast.FuncDef{
+ Name: name,
+ Params: params,
+ Body: body,
+ }, nil
+}
+
// parser holds the state for a single parse operation.
type parser struct {
tokens []token.Token
@@ -96,7 +215,7 @@
return left, nil
}
-// factor → NUMBER | '(' expr ')'
+// factor → NUMBER | IDENT | IDENT '(' args ')' | '(' expr ')'
func (p *parser) factor() (ast.Node, error) {
tok := p.current()
switch tok.Type {
@@ -108,6 +227,35 @@
}
return &ast.NumberLit{Value: val}, nil
+ case token.Ident:
+ p.advance()
+ // If followed by '(', this is a function call.
+ if p.current().Type == token.LParen {
+ p.advance() // consume '('
+ var args []ast.Node
+ if p.current().Type != token.RParen {
+ arg, err := p.expr()
+ if err != nil {
+ return nil, err
+ }
+ args = append(args, arg)
+ for p.current().Type == token.Comma {
+ p.advance() // consume ','
+ arg, err := p.expr()
+ if err != nil {
+ return nil, err
+ }
+ args = append(args, arg)
+ }
+ }
+ if _, err := p.expect(token.RParen); err != nil {
+ return nil, fmt.Errorf("expected ')' after function arguments at position %d", p.current().Pos)
+ }
+ return &ast.FuncCall{Name: tok.Literal, Args: args}, nil
+ }
+ // Otherwise, it's a variable reference.
+ return &ast.Ident{Name: tok.Literal}, nil
+
case token.LParen:
p.advance() // consume '('
node, err := p.expr()
diff --git a/parser/parser_test.go b/parser/parser_test.go
index 521455b..99b0e32 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -6,23 +6,14 @@
"testing"
)
-// helper: tokenize inline for concise tests
-func tokens(toks ...token.Token) []token.Token {
- return toks
-}
+// --- Parse (backward compatibility) ---
-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)
+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)
}
@@ -31,443 +22,433 @@
t.Fatalf("expected *ast.NumberLit, got %T", node)
}
if num.Value != 42 {
- t.Fatalf("expected 42, got %f", num.Value)
+ t.Fatalf("expected 42, got %v", num.Value)
}
}
-func TestParseDecimalNumber(t *testing.T) {
- toks := tokens(
- tok(token.Number, "3.14", 0),
- tok(token.EOF, "", 4),
- )
- node, err := Parse(toks)
+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)
}
- 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)
+ bin, 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)
+ if bin.Op != token.Plus {
+ t.Fatalf("expected Plus, got %v", bin.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)
+// --- 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)
}
- expr, ok := node.(*ast.BinaryExpr)
+ ident, ok := node.(*ast.Ident)
if !ok {
- t.Fatalf("expected *ast.BinaryExpr, got %T", node)
+ t.Fatalf("expected *ast.Ident, got %T", node)
}
- if expr.Op != token.Minus {
- t.Fatalf("expected Minus, got %v", expr.Op)
+ if ident.Name != "x" {
+ t.Fatalf("expected 'x', got %q", ident.Name)
}
- 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)
+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)
}
- expr, ok := node.(*ast.BinaryExpr)
+ bin, 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)
+ left, ok := bin.Left.(*ast.Ident)
+ if !ok {
+ t.Fatalf("expected left to be *ast.Ident, got %T", bin.Left)
}
- assertNumber(t, expr.Left, 2)
- assertNumber(t, expr.Right, 3)
+ if left.Name != "x" {
+ t.Fatalf("expected 'x', got %q", left.Name)
+ }
}
-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)
+// --- 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)
}
- expr, ok := node.(*ast.BinaryExpr)
+ fc, ok := node.(*ast.FuncCall)
if !ok {
- t.Fatalf("expected *ast.BinaryExpr, got %T", node)
+ t.Fatalf("expected *ast.FuncCall, got %T", node)
}
- if expr.Op != token.Slash {
- t.Fatalf("expected Slash, got %v", expr.Op)
+ if fc.Name != "f" {
+ t.Fatalf("expected 'f', got %q", fc.Name)
}
- assertNumber(t, expr.Left, 6)
- assertNumber(t, expr.Right, 2)
+ if len(fc.Args) != 0 {
+ t.Fatalf("expected 0 args, got %d", len(fc.Args))
+ }
}
-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)
+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)
}
- // Root should be Plus
- expr, ok := node.(*ast.BinaryExpr)
+ fc, ok := node.(*ast.FuncCall)
if !ok {
- t.Fatalf("expected *ast.BinaryExpr, got %T", node)
+ t.Fatalf("expected *ast.FuncCall, got %T", node)
}
- if expr.Op != token.Plus {
- t.Fatalf("expected Plus at root, got %v", expr.Op)
+ if fc.Name != "f" {
+ t.Fatalf("expected 'f', got %q", fc.Name)
}
- assertNumber(t, expr.Left, 1)
- // Right should be Star
- right, ok := expr.Right.(*ast.BinaryExpr)
+ 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 right to be *ast.BinaryExpr, got %T", expr.Right)
+ t.Fatalf("expected arg to be *ast.NumberLit, got %T", fc.Args[0])
}
- if right.Op != token.Star {
- t.Fatalf("expected Star, got %v", right.Op)
+ if arg.Value != 42 {
+ t.Fatalf("expected 42, got %v", arg.Value)
}
- 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)
+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)
}
- expr, ok := node.(*ast.BinaryExpr)
+ fc, ok := node.(*ast.FuncCall)
if !ok {
- t.Fatalf("expected *ast.BinaryExpr, got %T", node)
+ t.Fatalf("expected *ast.FuncCall, got %T", node)
}
- if expr.Op != token.Plus {
- t.Fatalf("expected Plus at root, got %v", expr.Op)
+ if len(fc.Args) != 3 {
+ t.Fatalf("expected 3 args, got %d", len(fc.Args))
}
- 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)
+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)
}
- // Root: (1 - 2) - 3
- expr, ok := node.(*ast.BinaryExpr)
+ fc, ok := node.(*ast.FuncCall)
if !ok {
- t.Fatalf("expected *ast.BinaryExpr, got %T", node)
+ t.Fatalf("expected *ast.FuncCall, got %T", node)
}
- if expr.Op != token.Minus {
- t.Fatalf("expected Minus at root, got %v", expr.Op)
+ if len(fc.Args) != 2 {
+ t.Fatalf("expected 2 args, got %d", len(fc.Args))
}
- assertNumber(t, expr.Right, 3)
- left, ok := expr.Left.(*ast.BinaryExpr)
+ // First arg: 1+2
+ _, ok = fc.Args[0].(*ast.BinaryExpr)
if !ok {
- t.Fatalf("expected left to be *ast.BinaryExpr, got %T", expr.Left)
+ t.Fatalf("expected first arg to be *ast.BinaryExpr, got %T", fc.Args[0])
}
- 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)
+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)
}
- expr, ok := node.(*ast.BinaryExpr)
+ bin, 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)
+ _, ok = bin.Left.(*ast.FuncCall)
if !ok {
- t.Fatalf("expected left to be *ast.BinaryExpr, got %T", expr.Left)
+ t.Fatalf("expected left to be *ast.FuncCall, got %T", bin.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)
+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)
}
- expr, ok := node.(*ast.BinaryExpr)
+ es, ok := stmt.(*ast.ExprStmt)
if !ok {
- t.Fatalf("expected *ast.BinaryExpr, got %T", node)
+ t.Fatalf("expected *ast.ExprStmt, got %T", stmt)
}
- if expr.Op != token.Plus {
- t.Fatalf("expected Plus, got %v", expr.Op)
+ _, ok = es.Expr.(*ast.BinaryExpr)
+ if !ok {
+ t.Fatalf("expected expr to be *ast.BinaryExpr, got %T", es.Expr)
}
- 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)
+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)
}
- // Root: (1 + (2*3)) - (4/2)
- root, ok := node.(*ast.BinaryExpr)
+ es, ok := stmt.(*ast.ExprStmt)
if !ok {
- t.Fatalf("expected *ast.BinaryExpr, got %T", node)
+ t.Fatalf("expected *ast.ExprStmt, got %T", stmt)
}
- 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)
+ _, ok = es.Expr.(*ast.FuncCall)
if !ok {
- t.Fatalf("expected left to be *ast.BinaryExpr, got %T", root.Left)
+ t.Fatalf("expected expr to be *ast.FuncCall, got %T", es.Expr)
}
- 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 ---
+// --- ParseLine: function definition ---
-func TestParseEmptyInput(t *testing.T) {
- toks := tokens(
- tok(token.EOF, "", 0),
- )
- _, err := Parse(toks)
+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 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)
+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 right paren")
+ t.Fatal("expected error for missing function body")
}
}
-func TestParseUnexpectedRParen(t *testing.T) {
- // ) 1
- toks := tokens(
- tok(token.RParen, ")", 0),
- tok(token.Number, "1", 2),
- tok(token.EOF, "", 3),
- )
- _, err := Parse(toks)
+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 unexpected right paren")
+ t.Fatal("expected error for numeric parameter in func def")
}
}
-func TestParseTrailingOperator(t *testing.T) {
- // 1 +
- toks := tokens(
- tok(token.Number, "1", 0),
- tok(token.Plus, "+", 2),
- tok(token.EOF, "", 3),
- )
- _, err := Parse(toks)
+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 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)
+ t.Fatal("expected error for trailing tokens after function body")
}
}