4.0 KiB
STATUS — Phase parse
DONE
All D1–D6 gates PASS (Adversary-verified @2026-06-15T05:00Z).
Gate D6 CLAIMED — PASS
WHAT: calc/test_parser.py passes under python -m unittest, 0 failures, 37 total tests (18 lexer + 19 parser), covering D1–D5.
HOW:
python -m unittest -q
EXPECTED:
Ran 37 tests in X.XXXs
OK
WHERE: calc/test_parser.py @ commit 866091c
Gate D5 CLAIMED — awaiting Adversary
WHAT: Each of "1 +", "(1", "1 2", ")(", and "" raises ParseError (not any other exception).
HOW:
python -c "
from calc.lexer import tokenize; from calc.parser import parse, ParseError
for src in ['1 +', '(1', '1 2', ')(', '']:
try:
parse(tokenize(src))
print(f'FAIL no error for {src!r}')
except ParseError as e:
print(f'OK ParseError for {src!r}: {e}')
except Exception as e:
print(f'FAIL wrong exception for {src!r}: {type(e).__name__}: {e}')
"
EXPECTED:
OK ParseError for '1 +': unexpected token 'EOF' (None)
OK ParseError for '(1': unclosed parenthesis, got 'EOF'
OK ParseError for '1 2': unexpected token 'NUMBER' (2)
OK ParseError for ')(': unexpected token 'RPAREN' (')')
OK ParseError for '': empty input
WHERE: calc/parser.py @ commit 866091c
Gate D4 CLAIMED — awaiting Adversary
WHAT: Unary minus parses correctly for -5, -(1+2), 3 * -2.
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(op='-', operand=Num(value=5))
Unary(op='-', operand=BinOp(op='+', left=Num(value=1), right=Num(value=2)))
BinOp(op='*', left=Num(value=3), right=Unary(op='-', operand=Num(value=2)))
WHERE: calc/parser.py @ commit 866091c
Gate D3 CLAIMED — awaiting Adversary
WHAT: Parens override precedence: (1+2)*3 parses as BinOp(*, BinOp(+, Num(1), Num(2)), Num(3)).
HOW:
python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('(1+2)*3')))"
EXPECTED:
BinOp(op='*', left=BinOp(op='+', left=Num(value=1), right=Num(value=2)), right=Num(value=3))
WHERE: calc/parser.py @ commit 866091c
Gate D2 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:
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(op='-', left=BinOp(op='-', left=Num(value=8), right=Num(value=3)), right=Num(value=2))
BinOp(op='/', left=BinOp(op='/', left=Num(value=8), right=Num(value=4)), right=Num(value=2))
WHERE: calc/parser.py @ commit 866091c
Gate D1 CLAIMED — awaiting Adversary
WHAT: * and / bind tighter than + and -: 1+2*3 parses as BinOp(+, Num(1), BinOp(*, Num(2), Num(3))).
HOW:
python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('1+2*3')))"
EXPECTED:
BinOp(op='+', left=Num(value=1), right=BinOp(op='*', left=Num(value=2), right=Num(value=3)))
WHERE: calc/parser.py @ commit 866091c
AST Shape
@dataclass
class Num:
value: int | float
@dataclass
class BinOp:
op: str # '+', '-', '*', '/'
left: Node
right: Node
@dataclass
class Unary:
op: str # '-'
operand: Node
Grammar:
expr = term (('+' | '-') term)*
term = unary (('*' | '/') unary)*
unary = '-' unary | primary
primary = NUMBER | '(' expr ')'