# JOURNAL — parse phase (Builder) ## 2026-06-15 — Initial implementation **Design decisions:** Grammar used (standard precedence climbing via two layers): ``` expr := term (('+' | '-') term)* term := factor (('*' | '/') factor)* factor := '-' factor | primary primary := NUMBER | '(' expr ')' ``` Left associativity falls out of the `while` loop pattern in `_expr()` and `_term()` — each new BinOp wraps the accumulated left node. Unary minus in `_factor()` recurses to itself, so `--5` → `Unary('-', Unary('-', Num(5)))` correctly. The placement in `_factor()` (between `_term()` and `_primary()`) means `3 * -2` works: `_term()` calls `_factor()` for the right operand, which detects the MINUS. **Verification runs:** D1: ``` $ python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('1+2*3')))" BinOp('+', Num(1), BinOp('*', Num(2), Num(3))) ``` D2: ``` $ python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('8-3-2')))" BinOp('-', BinOp('-', Num(8), Num(3)), Num(2)) $ python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('8/4/2')))" BinOp('/', BinOp('/', Num(8), Num(4)), Num(2)) ``` D3: ``` $ python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('(1+2)*3')))" BinOp('*', BinOp('+', Num(1), Num(2)), Num(3)) ``` D4: ``` $ python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('-5')))" Unary('-', Num(5)) $ python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('-(1+2)')))" Unary('-', BinOp('+', Num(1), Num(2))) $ python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('3 * -2')))" BinOp('*', Num(3), Unary('-', Num(2))) ``` D5 (all 5 required error cases + extras): ``` OK: '1 +' -> ParseError: unexpected token 'EOF' OK: '(1' -> ParseError: unclosed parenthesis, got 'EOF' OK: '1 2' -> ParseError: unexpected token 'NUMBER' OK: ')(' -> ParseError: unexpected token 'RPAREN' OK: '' -> ParseError: empty input ``` D6: ``` $ python -m unittest -q ---------------------------------------------------------------------- Ran 48 tests in 0.002s OK ```