# STATUS — phase parse ## AST Node Shapes ``` Num(value: int|float) BinOp(op: str, left: Node, right: Node) -- op in {'+','-','*','/'} Unary(op: str, operand: Node) -- op == '-' ``` All nodes are dataclasses with `__repr__` returning the form above. ## Exact Shape Assertions (for re-verification) ```bash python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('1+2*3')))" # Expected: BinOp('+', Num(1), BinOp('*', Num(2), Num(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)) python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('8-3-2')))" # Expected: 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')))" # Expected: BinOp('/', BinOp('/', Num(8), Num(4)), Num(2)) python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('-5')))" # Expected: Unary('-', Num(5)) python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('-(1+2)')))" # Expected: Unary('-', BinOp('+', Num(1), Num(2))) python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('3 * -2')))" # Expected: BinOp('*', Num(3), Unary('-', Num(2))) ``` ## Gates ### D1 — precedence **What:** `*` and `/` bind tighter than `+` and `-`. **Command:** `python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('1+2*3')))"` **Expected:** `BinOp('+', Num(1), BinOp('*', Num(2), Num(3)))` **Observed:** `BinOp('+', Num(1), BinOp('*', Num(2), Num(3)))` ✓ Also verified: `2*3+4` → `BinOp('+', BinOp('*', Num(2), Num(3)), Num(4))` ✓ Also verified: `10-6/2` → `BinOp('-', Num(10), BinOp('/', Num(6), Num(2)))` ✓ ### D2 — left associativity **What:** Same-precedence operators associate left. **Command:** `python -c "... print(parse(tokenize('8-3-2')))"` **Expected:** `BinOp('-', BinOp('-', Num(8), Num(3)), Num(2))` **Observed:** `BinOp('-', BinOp('-', Num(8), Num(3)), Num(2))` ✓ Also verified: `8/4/2` → `BinOp('/', BinOp('/', Num(8), Num(4)), Num(2))` ✓ Also verified: `1+2+3` → `BinOp('+', BinOp('+', Num(1), Num(2)), Num(3))` ✓ ### D3 — parentheses **What:** Parens override precedence. **Command:** `python -c "... print(parse(tokenize('(1+2)*3')))"` **Expected:** `BinOp('*', BinOp('+', Num(1), Num(2)), Num(3))` **Observed:** `BinOp('*', BinOp('+', Num(1), Num(2)), Num(3))` ✓ Also verified: `((2+3))` → `BinOp('+', Num(2), Num(3))` ✓ Also verified: `1*(2+3)*4` → `BinOp('*', BinOp('*', Num(1), BinOp('+', Num(2), Num(3))), Num(4))` ✓ ### D4 — unary minus **What:** Leading and nested unary minus parses. **Commands and observations:** - `-5` → `Unary('-', Num(5))` ✓ - `-(1+2)` → `Unary('-', BinOp('+', Num(1), Num(2)))` ✓ - `3 * -2` → `BinOp('*', Num(3), Unary('-', Num(2)))` ✓ - `--5` → `Unary('-', Unary('-', Num(5)))` ✓ ### D5 — errors **What:** Malformed input raises `ParseError` (not any other exception). **Command:** ```python from calc.lexer import tokenize from calc.parser import parse, ParseError cases = ['1 +', '(1', '1 2', ')(', ''] for src in cases: try: parse(tokenize(src)) print(f' FAIL no error for {src!r}') except ParseError as e: print(f' PASS ParseError for {src!r}: {e}') ``` **Observed:** ``` PASS ParseError for '1 +': unexpected end of input PASS ParseError for '(1': expected 'RPAREN' but got 'EOF' (None) PASS ParseError for '1 2': unexpected token 'NUMBER' (2) after expression PASS ParseError for ')(': unexpected token 'RPAREN' (')') PASS ParseError for '': empty input ``` ✓ ### D6 — tests green **Command:** `python -m unittest -q` **Expected:** 0 failures **Observed:** ``` ---------------------------------------------------------------------- Ran 32 tests in 0.001s OK ``` ✓ (19 parser tests + 13 lexer tests) ## DONE