| Sketch🕴️ | c3ec07d | 2026-02-28 20:35:06 +0400 | [diff] [blame^] | 1 | # Implementation Plan: Function Definitions |
| Sketch🕴️ | 719e823 | 2026-02-28 19:07:50 +0400 | [diff] [blame] | 2 | |
| Sketch🕴️ | c3ec07d | 2026-02-28 20:35:06 +0400 | [diff] [blame^] | 3 | ## Overview |
| 4 | Bottom-up implementation through the stack: token → ast → lexer → parser → evaluator → repl → integration tests. Each step maintains backward compatibility and follows TDD. |
| Sketch🕴️ | 719e823 | 2026-02-28 19:07:50 +0400 | [diff] [blame] | 5 | |
| Sketch🕴️ | c3ec07d | 2026-02-28 20:35:06 +0400 | [diff] [blame^] | 6 | ## Steps |
| Sketch🕴️ | 719e823 | 2026-02-28 19:07:50 +0400 | [diff] [blame] | 7 | |
| Sketch🕴️ | c3ec07d | 2026-02-28 20:35:06 +0400 | [diff] [blame^] | 8 | ### Step 1: Token layer (`token/token.go`) |
| 9 | - Add `Ident`, `Comma`, `Equals` constants to `Type` enum |
| 10 | - Update `String()` for new types |
| 11 | - No tests needed — pure data types |
| Sketch🕴️ | 719e823 | 2026-02-28 19:07:50 +0400 | [diff] [blame] | 12 | |
| Sketch🕴️ | c3ec07d | 2026-02-28 20:35:06 +0400 | [diff] [blame^] | 13 | ### Step 2: AST layer (`ast/ast.go`) |
| 14 | - Add `Ident` struct: `Name string`; implements `Node` |
| 15 | - Add `FuncCall` struct: `Name string`, `Args []Node`; implements `Node` |
| 16 | - Add `Statement` interface with sealed `stmt()` marker |
| 17 | - Add `ExprStmt` struct: `Expr Node`; implements `Statement` |
| 18 | - Add `FuncDef` struct: `Name string`, `Params []string`, `Body Node`; implements `Statement` |
| 19 | - No tests needed — pure data types |
| Sketch🕴️ | 719e823 | 2026-02-28 19:07:50 +0400 | [diff] [blame] | 20 | |
| Sketch🕴️ | c3ec07d | 2026-02-28 20:35:06 +0400 | [diff] [blame^] | 21 | ### Step 3: Lexer (`lexer/lexer.go`) |
| 22 | - Add `isLetter(ch byte) bool` helper |
| 23 | - Before the single-char switch, add branch: if `isLetter(ch)`, scan identifier (letter then letters/digits), emit `Ident` token |
| 24 | - Add `','` → `Comma` and `'='` → `Equals` to single-char switch |
| 25 | - **Tests:** identifiers (`x`, `foo`, `f1`), comma, equals, full definition `f(x) = x + 1`, call `f(1, 2)`, mixed with numbers |
| Sketch🕴️ | 719e823 | 2026-02-28 19:07:50 +0400 | [diff] [blame] | 26 | |
| Sketch🕴️ | c3ec07d | 2026-02-28 20:35:06 +0400 | [diff] [blame^] | 27 | ### Step 4: Parser (`parser/parser.go`) |
| 28 | - Extend `factor()`: |
| 29 | - `Ident` followed by `LParen` → parse `FuncCall`: consume `(`, parse args as comma-separated exprs, consume `)` |
| 30 | - `Ident` not followed by `LParen` → return `&ast.Ident{Name}` |
| 31 | - Add `parseFuncDef()`: expects `Ident(` params `) = expr` |
| 32 | - Add `ParseLine(tokens) (Statement, error)`: |
| 33 | - Scan for `Equals` token (not inside parens) |
| 34 | - If found → `parseFuncDef()` → `*ast.FuncDef` |
| 35 | - If not → `expr()` → `*ast.ExprStmt{Expr}` |
| 36 | - Keep `Parse()` unchanged for backward compat |
| 37 | - **Tests:** ParseLine for defs and exprs, factor for ident and func call, error cases |
| Sketch🕴️ | 719e823 | 2026-02-28 19:07:50 +0400 | [diff] [blame] | 38 | |
| Sketch🕴️ | c3ec07d | 2026-02-28 20:35:06 +0400 | [diff] [blame^] | 39 | ### Step 5: Evaluator (`evaluator/evaluator.go`) |
| 40 | - Add `Evaluator` struct with `funcs map[string]*ast.FuncDef` |
| 41 | - `New() *Evaluator` |
| 42 | - `Define(def *ast.FuncDef) error` — error on redefinition |
| 43 | - `Eval(node ast.Node, env map[string]float64) (float64, error)`: |
| 44 | - `*ast.NumberLit` → return value |
| 45 | - `*ast.BinaryExpr` → recurse left/right with same env |
| 46 | - `*ast.Ident` → lookup in env, error if not found |
| 47 | - `*ast.FuncCall` → lookup func, eval args in caller env, bind params, eval body in new env |
| 48 | - Keep package-level `Eval(node) (float64, error)` as backward-compat wrapper |
| 49 | - **Tests:** all existing tests still pass, new tests for Ident, FuncCall, Define, errors |
| Sketch🕴️ | 719e823 | 2026-02-28 19:07:50 +0400 | [diff] [blame] | 50 | |
| Sketch🕴️ | c3ec07d | 2026-02-28 20:35:06 +0400 | [diff] [blame^] | 51 | ### Step 6: REPL (`repl/repl.go`) |
| 52 | - In `Run()`: create `evaluator.New()` before loop |
| 53 | - Replace `evalLine()` with inline logic using `ParseLine()` |
| 54 | - `*ast.FuncDef` → `ev.Define(def)`, print `"defined <name>"` |
| 55 | - `*ast.ExprStmt` → `ev.Eval(stmt.Expr, nil)`, print result |
| 56 | - **Tests:** define + call across lines, redefine error, undefined func error |
| Sketch🕴️ | 719e823 | 2026-02-28 19:07:50 +0400 | [diff] [blame] | 57 | |
| Sketch🕴️ | c3ec07d | 2026-02-28 20:35:06 +0400 | [diff] [blame^] | 58 | ### Step 7: Integration tests (`integration_test.go`) |
| 59 | - Update `eval()`/`evalErr()` helpers to use `Evaluator` struct |
| 60 | - Add tests: |
| 61 | - Define and call single-param function |
| 62 | - Define and call multi-param function |
| 63 | - Cross-function calls |
| 64 | - Nested function calls in expressions |
| 65 | - Error: undefined function |
| 66 | - Error: wrong argument count |
| 67 | - Error: function redefinition |
| 68 | - Error: undefined variable |
| Sketch🕴️ | 719e823 | 2026-02-28 19:07:50 +0400 | [diff] [blame] | 69 | |
| Sketch🕴️ | c3ec07d | 2026-02-28 20:35:06 +0400 | [diff] [blame^] | 70 | ## Backward Compatibility |
| 71 | - `Parse()` remains unchanged — returns `ast.Node` |
| 72 | - Package-level `Eval()` remains — wraps `New().Eval(node, nil)` |
| 73 | - Existing tests must continue to pass at every step |