artifacts: add calculators/ — the 30 built calculators (5/variant) + machine-docs + git logs
This commit is contained in:
@ -0,0 +1,44 @@
|
||||
# JOURNAL-parse.md
|
||||
|
||||
## 2026-06-15T01:21Z — Parser implementation
|
||||
|
||||
### Approach
|
||||
Implemented a classic recursive-descent parser with three precedence levels:
|
||||
- `_expr()`: handles `+` / `-` (lowest precedence, left-associative)
|
||||
- `_term()`: handles `*` / `/` (middle precedence, left-associative)
|
||||
- `_unary()`: handles unary `-` (right-associative by recursion)
|
||||
- `_primary()`: handles NUMBER literals and `(` expr `)`
|
||||
|
||||
Left-associativity falls out naturally from the while-loop accumulation pattern in `_expr()` and `_term()`: each iteration wraps the running `node` as the left child of a new `BinOp`.
|
||||
|
||||
Unary minus recurses into itself (`_unary()` calls `_unary()`) which gives right-associativity for `--5` → `Unary('-', Unary('-', Num(5)))`.
|
||||
|
||||
### Verification run
|
||||
```
|
||||
$ python -m unittest -q
|
||||
Ran 43 tests in 0.001s
|
||||
OK
|
||||
```
|
||||
|
||||
Manual shape checks:
|
||||
```
|
||||
D1 1+2*3: BinOp('+', Num(1), BinOp('*', Num(2), Num(3))) ✓
|
||||
D2 8-3-2: BinOp('-', BinOp('-', Num(8), Num(3)), Num(2)) ✓
|
||||
D2 8/4/2: BinOp('/', BinOp('/', Num(8), Num(4)), Num(2)) ✓
|
||||
D3 (1+2)*3: BinOp('*', BinOp('+', Num(1), Num(2)), Num(3)) ✓
|
||||
D4 -5: Unary('-', Num(5)) ✓
|
||||
D4 -(1+2): Unary('-', BinOp('+', Num(1), Num(2))) ✓
|
||||
D4 3*-2: BinOp('*', Num(3), Unary('-', Num(2))) ✓
|
||||
D5 '1 +': ParseError: unexpected end of input ✓
|
||||
D5 '(1': ParseError: expected 'RPAREN' but got 'EOF' ✓
|
||||
D5 '1 2': ParseError: unexpected token after expression ✓
|
||||
D5 ')(': ParseError: unexpected token 'RPAREN' ✓
|
||||
D5 '': ParseError: empty expression ✓
|
||||
```
|
||||
|
||||
### Error handling design
|
||||
- Empty token list (just EOF): caught in `parse()` before entering `_expr()`
|
||||
- Trailing operator (`1 +`): `_primary()` sees EOF, raises ParseError
|
||||
- Unclosed paren (`(1`): `_consume('RPAREN')` fails with ParseError
|
||||
- Extra number (`1 2`): `parse()` checks `peek() != EOF` after `_expr()` returns
|
||||
- `)` before `(`: `_primary()` sees RPAREN, not a valid primary, raises ParseError
|
||||
Reference in New Issue
Block a user