5.0 KiB
STATUS — phase parse
Role: Builder owns this file.
DONE
All gates PASSED by Adversary (2026-06-15T05:14:00Z). Advisory F1 corrected post-PASS.
| Gate | Status |
|---|---|
| D1 | PASS (Adversary verified 2026-06-15T05:12:00Z) |
| D2 | PASS (Adversary verified 2026-06-15T05:12:30Z) |
| D3 | PASS (Adversary verified 2026-06-15T05:13:00Z) |
| D4 | PASS (Adversary verified 2026-06-15T05:13:30Z) |
| D5 | PASS (Adversary verified 2026-06-15T05:13:45Z) |
| D6 | PASS (Adversary verified 2026-06-15T05:14:00Z) |
Post-DONE fix: Advisory F1 resolved — corrected test count from "50 (25+25)" to "48 (23+25)" in D6 gate entry.
AST node shapes (stable interface)
calc/parser.py exports three node types and one exception:
@dataclass
class Num:
value: Union[int, float]
# repr: Num(42) or Num(3.14)
@dataclass
class BinOp:
op: str # one of '+', '-', '*', '/'
left: Node
right: Node
# repr: BinOp('+', Num(1), Num(2))
@dataclass
class Unary:
op: str # '-'
operand: Node
# repr: Unary('-', Num(5))
class ParseError(Exception): ...
parse(tokens) -> Node consumes a token list from calc.lexer.tokenize().
Gate D1 — Precedence
WHAT: * and / bind tighter than + and -. 1+2*3 parses as 1+(2*3), not (1+2)*3.
HOW to verify:
python -c "
from calc.lexer import tokenize; from calc.parser import parse
r = repr(parse(tokenize('1+2*3')))
assert r == \"BinOp('+', Num(1), BinOp('*', Num(2), Num(3)))\", r
print('D1 OK:', r)
"
python -c "
from calc.lexer import tokenize; from calc.parser import parse
r = repr(parse(tokenize('2*3+1')))
assert r == \"BinOp('+', BinOp('*', Num(2), Num(3)), Num(1))\", r
print('D1b OK:', r)
"
EXPECTED:
D1 OK: BinOp('+', Num(1), BinOp('*', Num(2), Num(3)))
D1b OK: BinOp('+', BinOp('*', Num(2), Num(3)), Num(1))
WHERE: calc/parser.py (current HEAD)
Gate D2 — Left Associativity
WHAT: Same-precedence operators associate left. 8-3-2 → (8-3)-2; 8/4/2 → (8/4)/2.
HOW to verify:
python -c "
from calc.lexer import tokenize; from calc.parser import parse
r = repr(parse(tokenize('8-3-2')))
assert r == \"BinOp('-', BinOp('-', Num(8), Num(3)), Num(2))\", r
print('D2 sub OK:', r)
"
python -c "
from calc.lexer import tokenize; from calc.parser import parse
r = repr(parse(tokenize('8/4/2')))
assert r == \"BinOp('/', BinOp('/', Num(8), Num(4)), Num(2))\", r
print('D2 div OK:', r)
"
EXPECTED:
D2 sub OK: BinOp('-', BinOp('-', Num(8), Num(3)), Num(2))
D2 div OK: BinOp('/', BinOp('/', Num(8), Num(4)), Num(2))
WHERE: calc/parser.py (current HEAD)
Gate D3 — Parentheses
WHAT: Parens override precedence. (1+2)*3 parses with + under *.
HOW to verify:
python -c "
from calc.lexer import tokenize; from calc.parser import parse
r = repr(parse(tokenize('(1+2)*3')))
assert r == \"BinOp('*', BinOp('+', Num(1), Num(2)), Num(3))\", r
print('D3 OK:', r)
"
python -c "
from calc.lexer import tokenize; from calc.parser import parse
r = repr(parse(tokenize('8/(2+2)')))
assert r == \"BinOp('/', Num(8), BinOp('+', Num(2), Num(2)))\", r
print('D3b OK:', r)
"
EXPECTED:
D3 OK: BinOp('*', BinOp('+', Num(1), Num(2)), Num(3))
D3b OK: BinOp('/', Num(8), BinOp('+', Num(2), Num(2)))
WHERE: calc/parser.py (current HEAD)
Gate D4 — Unary Minus
WHAT: Leading and nested unary minus parses correctly.
HOW to verify:
python -c "
from calc.lexer import tokenize; from calc.parser import parse
r = repr(parse(tokenize('-5')))
assert r == \"Unary('-', Num(5))\", r
print('D4a OK:', r)
r = repr(parse(tokenize('-(1+2)')))
assert r == \"Unary('-', BinOp('+', Num(1), Num(2)))\", r
print('D4b OK:', r)
r = repr(parse(tokenize('3 * -2')))
assert r == \"BinOp('*', Num(3), Unary('-', Num(2)))\", r
print('D4c OK:', r)
"
EXPECTED:
D4a OK: Unary('-', Num(5))
D4b OK: Unary('-', BinOp('+', Num(1), Num(2)))
D4c OK: BinOp('*', Num(3), Unary('-', Num(2)))
WHERE: calc/parser.py (current HEAD)
Gate D5 — Errors
WHAT: Malformed inputs raise ParseError. Mandated cases: "1 +", "(1", "1 2", ")(", "".
HOW to verify:
python -c "
from calc.lexer import tokenize
from calc.parser import parse, ParseError
bad_cases = ['1 +', '(1', '1 2', ')(', '']
for src in bad_cases:
try:
parse(tokenize(src))
print('FAIL: no exception for', repr(src))
except ParseError as e:
print('OK ParseError for', repr(src), ':', e)
except Exception as e:
print('FAIL: wrong exception', type(e).__name__, 'for', repr(src), ':', e)
"
EXPECTED: Five lines all starting with OK ParseError for.
WHERE: calc/parser.py (current HEAD)
Gate D6 — Tests Green
WHAT: calc/test_parser.py (unittest) passes under python -m unittest, 0 failures, covering D1–D5.
HOW to verify:
python -m unittest -q
EXPECTED: Ran 48 tests in 0.00Xs OK (23 lexer + 25 parser)
WHERE: calc/test_parser.py and calc/parser.py (current HEAD)