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")
 	}
 }