# STATUS — parse phase (Builder) ## DONE All DoD gates (D1–D6) verified PASS by Adversary @2026-06-15T06:13:00Z. No veto. Phase complete. ## Current state Implementation complete. `calc/parser.py` and `calc/test_parser.py` created. 48 tests pass (24 lex + 24 parser). All gates claimed and Adversary-verified. ## AST Shape Reference ``` Num(value) — value is int or float BinOp(op, left, right) — op in ('+', '-', '*', '/') Unary(op, operand) — op is '-' ``` `repr()` of each node class is canonical (used in test assertions and verification commands). --- ## Gates ### D1 — precedence: **CLAIMED**, awaiting Adversary **WHAT:** `*` and `/` bind tighter than `+` and `-`. `1+2*3` → `BinOp('+', Num(1), BinOp('*', Num(2), Num(3)))` (not `BinOp('*', BinOp('+', ...), ...)`). **HOW to verify:** ```bash python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('1+2*3')))" python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('2*3+1')))" python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('5-2*3')))" ``` **EXPECTED:** ``` BinOp('+', Num(1), BinOp('*', Num(2), Num(3))) BinOp('+', BinOp('*', Num(2), Num(3)), Num(1)) BinOp('-', Num(5), BinOp('*', Num(2), Num(3))) ``` **WHERE:** `calc/parser.py` — `_expr()` calls `_term()` which handles `*`/`/`; `_expr()` handles `+`/`-` at lower priority. --- ### D2 — left associativity: **CLAIMED**, awaiting Adversary **WHAT:** Same-precedence operators associate left. `8-3-2` → `BinOp('-', BinOp('-', Num(8), Num(3)), Num(2))`. `8/4/2` → `BinOp('/', BinOp('/', Num(8), Num(4)), Num(2))`. **HOW to verify:** ```bash python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('8-3-2')))" python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('8/4/2')))" ``` **EXPECTED:** ``` BinOp('-', BinOp('-', Num(8), Num(3)), Num(2)) BinOp('/', BinOp('/', Num(8), Num(4)), Num(2)) ``` **WHERE:** `calc/parser.py` — `_expr()` and `_term()` use `while` loops, wrapping the accumulating left side. --- ### D3 — parentheses: **CLAIMED**, awaiting Adversary **WHAT:** Parens override precedence. `(1+2)*3` → `BinOp('*', BinOp('+', Num(1), Num(2)), Num(3))`. **HOW to verify:** ```bash python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('(1+2)*3')))" python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('1*(2+3)')))" ``` **EXPECTED:** ``` BinOp('*', BinOp('+', Num(1), Num(2)), Num(3)) BinOp('*', Num(1), BinOp('+', Num(2), Num(3))) ``` **WHERE:** `calc/parser.py` — `_primary()` handles `LPAREN … RPAREN` by calling `_expr()` recursively. --- ### D4 — unary minus: **CLAIMED**, awaiting Adversary **WHAT:** Leading and nested unary minus parses correctly. `-5` → `Unary('-', Num(5))`. `-(1+2)` → `Unary('-', BinOp('+', Num(1), Num(2)))`. `3 * -2` → `BinOp('*', Num(3), Unary('-', Num(2)))`. **HOW to verify:** ```bash python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('-5')))" python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('-(1+2)')))" python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('3 * -2')))" ``` **EXPECTED:** ``` Unary('-', Num(5)) Unary('-', BinOp('+', Num(1), Num(2))) BinOp('*', Num(3), Unary('-', Num(2))) ``` **WHERE:** `calc/parser.py` — `_factor()` detects `MINUS` and recurses, returning `Unary('-', operand)`. --- ### D5 — errors: **CLAIMED**, awaiting Adversary **WHAT:** Malformed input raises `ParseError` (not `LexError`, `IndexError`, etc.). Five required cases: `"1 +"`, `"(1"`, `"1 2"`, `")("`, `""`. **HOW to verify:** ```bash python -c " from calc.lexer import tokenize from calc.parser import parse, ParseError errors = ['1 +', '(1', '1 2', ')(', ''] for src in errors: try: parse(tokenize(src)) print(f'FAIL: {src!r} did not raise') except ParseError as e: print(f'OK: {src!r} -> ParseError: {e}') except Exception as e: print(f'FAIL: {src!r} raised wrong exception: {type(e).__name__}: {e}') " ``` **EXPECTED:** ``` 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 ``` **WHERE:** `calc/parser.py` — `parse()`, `_primary()`, `_primary()` RPAREN check. --- ### D6 — tests green: **CLAIMED**, awaiting Adversary **WHAT:** `calc/test_parser.py` + `calc/test_lexer.py` combined: 48 tests, 0 failures under `python -m unittest`. **HOW to verify:** ```bash python -m unittest -q ``` **EXPECTED:** ``` ---------------------------------------------------------------------- Ran 48 tests in 0.002s OK ``` **WHERE:** `calc/test_parser.py` (24 tests covering D1–D5), `calc/test_lexer.py` (24 tests from lex phase).