3.4 KiB
STATUS — Phase parse (Builder)
DONE
All gates D1–D6: Adversary-verified PASS (see REVIEW-parse.md @2026-06-15T04:08Z). Phase complete.
AST Node Shapes (stable API for evaluator)
Num(value) # numeric literal; value is int or float
BinOp(op, left, right) # op in {'+', '-', '*', '/'}; left/right are nodes
Unary(op, operand) # op is '-'; operand is a node
All nodes implement __repr__ and __eq__.
Gate Claims
D1 — Precedence
WHAT: * and / bind tighter than + and -; 1+2*3 parses as 1+(2*3).
HOW:
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)))
WHERE: calc/parser.py — _expr iterates +/-, _term iterates *//, so * folds first.
D2 — Left Associativity
WHAT: 8-3-2 parses as (8-3)-2; 8/4/2 as (8/4)/2.
HOW:
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—_exprand_termusewhileloops (iterative left fold).
D3 — Parentheses
WHAT: (1+2)*3 parses with + under *.
HOW:
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))
WHERE: calc/parser.py — _primary handles LPAREN expr RPAREN, returning inner node to be used in _term.
D4 — Unary Minus
WHAT: -5, -(1+2), 3 * -2 each parse correctly.
HOW:
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—_unaryhandlesMINUSrecursively before_primary.
D5 — Errors
WHAT: Malformed inputs raise ParseError (not any other exception).
HOW:
python -c "from calc.lexer import tokenize; from calc.parser import parse, ParseError
try:
parse(tokenize('1 +'))
print('NO ERROR')
except ParseError:
print('ParseError OK')
"
# Repeat for each case: '(1', '1 2', ')(', ''
EXPECTED: ParseError raised for all five inputs: "1 +", "(1", "1 2", ")(", "".
Shortcut — test suite already covers all five (TestErrors class).
WHERE: calc/parser.py — _primary raises on bad token; parse() raises on trailing token or empty input; _expect raises on mismatched RPAREN.
D6 — Tests Green
WHAT: python -m unittest -q passes, 0 failures; covers D1–D5 with structural assertions.
HOW:
python -m unittest -q
EXPECTED: Ran 44 tests in ...s\n\nOK (21 lexer + 23 parser)
WHERE: calc/test_parser.py — classes TestPrecedence, TestLeftAssociativity, TestParentheses, TestUnaryMinus, TestErrors.