Bottom-up implementation through the stack: token → ast → lexer → parser → evaluator → repl → integration tests. Each step maintains backward compatibility and follows TDD.
token/token.go)Ident, Comma, Equals constants to Type enumString() for new typesast/ast.go)Ident struct: Name string; implements NodeFuncCall struct: Name string, Args []Node; implements NodeStatement interface with sealed stmt() markerExprStmt struct: Expr Node; implements StatementFuncDef struct: Name string, Params []string, Body Node; implements Statementlexer/lexer.go)isLetter(ch byte) bool helperisLetter(ch), scan identifier (letter then letters/digits), emit Ident token',' → Comma and '=' → Equals to single-char switchx, foo, f1), comma, equals, full definition f(x) = x + 1, call f(1, 2), mixed with numbersparser/parser.go)factor():Ident followed by LParen → parse FuncCall: consume (, parse args as comma-separated exprs, consume )Ident not followed by LParen → return &ast.Ident{Name}parseFuncDef(): expects Ident( params ) = exprParseLine(tokens) (Statement, error):Equals token (not inside parens)parseFuncDef() → *ast.FuncDefexpr() → *ast.ExprStmt{Expr}Parse() unchanged for backward compatevaluator/evaluator.go)Evaluator struct with funcs map[string]*ast.FuncDefNew() *EvaluatorDefine(def *ast.FuncDef) error — error on redefinitionEval(node ast.Node, env map[string]float64) (float64, error):*ast.NumberLit → return value*ast.BinaryExpr → recurse left/right with same env*ast.Ident → lookup in env, error if not found*ast.FuncCall → lookup func, eval args in caller env, bind params, eval body in new envEval(node) (float64, error) as backward-compat wrapperrepl/repl.go)Run(): create evaluator.New() before loopevalLine() with inline logic using ParseLine()*ast.FuncDef → ev.Define(def), print "defined <name>"*ast.ExprStmt → ev.Eval(stmt.Expr, nil), print resultintegration_test.go)eval()/evalErr() helpers to use Evaluator structParse() remains unchanged — returns ast.NodeEval() remains — wraps New().Eval(node, nil)