artifacts: add calculators/ — the 30 built calculators (5/variant) + machine-docs + git logs
This commit is contained in:
@ -0,0 +1,9 @@
|
||||
# BACKLOG — Phase eval (Builder)
|
||||
|
||||
## Build backlog
|
||||
|
||||
- [x] D1 — arithmetic: CLAIMED
|
||||
- [ ] D2 — division: pending Adversary PASS on D1
|
||||
- [ ] D3 — result type: pending Adversary PASS on D1
|
||||
- [ ] D4 — CLI: pending Adversary PASS on D1
|
||||
- [ ] D5 — tests green: pending Adversary PASSes on D1-D4
|
||||
@ -0,0 +1,19 @@
|
||||
# BACKLOG — Phase lex
|
||||
|
||||
## Build backlog
|
||||
|
||||
- [x] Create calc/ package with __init__.py
|
||||
- [x] Implement calc/lexer.py: Token dataclass, LexError, tokenize()
|
||||
- [x] Implement calc/test_lexer.py: unittest 15 tests covering D1-D3
|
||||
- [x] Run tests — 15/15 PASS
|
||||
- [ ] Claim D1 (numbers) → await PASS
|
||||
- [ ] Claim D2 (operators & parens) → await PASS
|
||||
- [ ] Claim D3 (whitespace & errors) → await PASS
|
||||
- [ ] Claim D4 (tests green) → await PASS
|
||||
|
||||
## Adversary findings
|
||||
|
||||
### AF-1 (non-blocking): bare `.` leaks `ValueError` instead of `LexError`
|
||||
- Repro: `tokenize('.')` → `ValueError: could not convert string to float: '.'`
|
||||
- Expected: `LexError` (or any error that doesn't expose Python internals)
|
||||
- Status: OPEN, non-blocking — not required by DoD, but may bite future phases
|
||||
@ -0,0 +1,18 @@
|
||||
# BACKLOG — Phase parse (Builder)
|
||||
|
||||
## Build backlog
|
||||
|
||||
- [x] Read phase plan
|
||||
- [ ] Implement calc/parser.py (Num, BinOp, Unary, parse(), ParseError)
|
||||
- [ ] Implement calc/test_parser.py
|
||||
- [ ] Run tests locally — confirm green
|
||||
- [ ] Claim D1 (precedence)
|
||||
- [ ] Claim D2 (left associativity)
|
||||
- [ ] Claim D3 (parentheses)
|
||||
- [ ] Claim D4 (unary minus)
|
||||
- [ ] Claim D5 (errors)
|
||||
- [ ] Claim D6 (tests green)
|
||||
|
||||
## Adversary findings
|
||||
|
||||
_None yet._
|
||||
@ -0,0 +1,3 @@
|
||||
# DECISIONS — Phase lex (shared, append-only)
|
||||
|
||||
_No decisions recorded yet._
|
||||
@ -0,0 +1,27 @@
|
||||
# JOURNAL — Phase eval (Builder)
|
||||
|
||||
## 2026-06-15 — Implementation
|
||||
|
||||
Built evaluator, CLI, and test suite in one pass.
|
||||
|
||||
AST walk in `calc/evaluator.py`:
|
||||
- `Num`: return `node.value` (int or float as stored by lexer)
|
||||
- `Unary('-')`: negate result of recursive evaluate
|
||||
- `BinOp('+'/'-'/'*')`: straightforward arithmetic on evaluated children
|
||||
- `BinOp('/')`: true division; guard `right == 0` → `EvalError`; if result is float and whole, return `int(result)` (D3 rule)
|
||||
|
||||
D3 result type rule: `evaluate()` returns `int` for whole-valued results, `float` for non-whole. This means `4/2` → `int(2)` (prints `2`), `7/2` → `float(3.5)` (prints `3.5`). Pure integer arithmetic stays int throughout (Python int + int = int).
|
||||
|
||||
CLI `calc.py`: catches `LexError`, `ParseError`, `EvalError` → stderr + exit 1. No traceback exposed.
|
||||
|
||||
Test run: 46 tests pass (15 lexer + 20 parser + 11 evaluator). No regressions.
|
||||
|
||||
Verified all plan cases:
|
||||
```
|
||||
python calc.py "2+3*4" → 14
|
||||
python calc.py "(2+3)*4" → 20
|
||||
python calc.py "7/2" → 3.5
|
||||
python calc.py "4/2" → 2
|
||||
python calc.py "1/0" → error to stderr, exit 1
|
||||
python calc.py "1 +" → error to stderr, exit 1
|
||||
```
|
||||
@ -0,0 +1,21 @@
|
||||
# JOURNAL — Phase lex (Adversary)
|
||||
|
||||
## 2026-06-15T06:23:05Z — Adversary initialized
|
||||
|
||||
- Read phase plan from /home/loops/project-orchestrator/projects/agent-orchestrator-benchmark/plans/calc/lex.md
|
||||
- DoD gates: D1 (numbers), D2 (operators & parens), D3 (whitespace & errors), D4 (tests green)
|
||||
- Builder has not yet created STATUS-lex.md or any code
|
||||
- Initialized REVIEW-lex.md and BACKLOG-lex.md
|
||||
- Going idle; will check again in 10 min
|
||||
|
||||
## 2026-06-15T06:30:00Z — Builder: initial implementation complete
|
||||
|
||||
- Created calc/__init__.py, calc/lexer.py, calc/test_lexer.py
|
||||
- Token is a dataclass with kind:str and value:int|float|str
|
||||
- LexError is a plain Exception subclass
|
||||
- tokenize() loops over src with index, handles: whitespace skip, NUMBER (int+float incl. leading/trailing dot), operators/parens via dict, raises LexError for unknowns
|
||||
- 15 unittest tests, all PASS: `Ran 15 tests in 0.000s OK`
|
||||
- Plan verify commands output:
|
||||
- `tokenize('3.5*(1-2)')` → `[('NUMBER', 3.5), ('STAR', '*'), ('LPAREN', '('), ('NUMBER', 1), ('MINUS', '-'), ('NUMBER', 2), ('RPAREN', ')'), ('EOF', '')]`
|
||||
- `tokenize('1 @ 2')` → `LexError: unexpected character '@' at position 2`
|
||||
- Claiming D1, D2, D3, D4 in one commit
|
||||
@ -0,0 +1,56 @@
|
||||
# JOURNAL — Phase parse (Builder)
|
||||
|
||||
## 2026-06-15 — Start
|
||||
|
||||
Reading phase plan. Lex phase is DONE. Building parser on top of `calc/lexer.py`.
|
||||
|
||||
### Design
|
||||
|
||||
Grammar (recursive descent):
|
||||
```
|
||||
expr → term (('+' | '-') term)*
|
||||
term → unary (('*' | '/') unary)*
|
||||
unary → '-' unary | primary
|
||||
primary → NUMBER | '(' expr ')'
|
||||
```
|
||||
|
||||
- `expr`/`term` loop = left-associative by construction
|
||||
- `term` deeper than `expr` = `*`/`/` higher precedence than `+`/`-`
|
||||
- `unary` recursive = right-associative unary (`--5` parses as `-(-5)`)
|
||||
|
||||
AST nodes:
|
||||
- `Num(value)` — leaf
|
||||
- `BinOp(op, left, right)` — binary operator
|
||||
- `Unary(op, operand)` — unary operator
|
||||
|
||||
ParseError raised for: empty string, trailing operator, unclosed paren, consecutive numbers, mismatched parens.
|
||||
|
||||
## 2026-06-15 — Implementation + tests run
|
||||
|
||||
```
|
||||
$ python -m unittest -q
|
||||
Ran 35 tests in 0.001s
|
||||
OK
|
||||
```
|
||||
(15 from test_lexer + 20 from test_parser)
|
||||
|
||||
Plan verify commands:
|
||||
```
|
||||
$ python -c "...parse(tokenize('1+2*3'))"
|
||||
BinOp('+', Num(1), BinOp('*', Num(2), Num(3))) # D1 ✓
|
||||
|
||||
$ python -c "...parse(tokenize('(1+2)*3'))"
|
||||
BinOp('*', BinOp('+', Num(1), Num(2)), Num(3)) # D3 ✓
|
||||
|
||||
$ python -c "...parse(tokenize('8-3-2'))"
|
||||
BinOp('-', BinOp('-', Num(8), Num(3)), Num(2)) # D2 ✓
|
||||
|
||||
$ python -c "...parse(tokenize('-5'))"
|
||||
Unary('-', Num(5)) # D4 ✓
|
||||
|
||||
$ python -c "...parse(tokenize('3 * -2'))"
|
||||
BinOp('*', Num(3), Unary('-', Num(2))) # D4 ✓
|
||||
|
||||
$ python -c "...parse(tokenize('1 +'))"
|
||||
calc.parser.ParseError: unexpected end of expression # D5 ✓
|
||||
```
|
||||
@ -0,0 +1,37 @@
|
||||
# REVIEW — Phase eval (Adversary)
|
||||
|
||||
## Status
|
||||
All D1–D5 verified PASS. Awaiting Builder DONE declaration.
|
||||
|
||||
## Gate verdicts
|
||||
|
||||
### eval/D1: PASS @2026-06-15T06:37Z
|
||||
|
||||
Cold run of all 5 plan cases: 2+3*4→14, (2+3)*4→20, 8-3-2→3, -2+5→3, 2*-3→-6 all correct.
|
||||
Adversarial extras (1+2+3→6, 10-2*3→4, 6/2→3, 2*3+4*5→26, -(3)→-3, -(-5)→5) all correct.
|
||||
Implementation in calc/evaluator.py (commit 0fc263d) handles Num, BinOp, Unary nodes correctly.
|
||||
|
||||
### eval/D2: PASS @2026-06-15T06:43Z
|
||||
|
||||
7/2→3.5 confirmed. 1/0 raises EvalError (not ZeroDivisionError). ZeroDivisionError does not escape.
|
||||
Adversarial: 5/(2-2) and 3/(1-1) both raise EvalError; 9/3→3 (int), 10/4→2.5 (float).
|
||||
|
||||
### eval/D3: PASS @2026-06-15T06:43Z
|
||||
|
||||
4/2→2 (int, no .0), 7/2→3.5 (float), 2+3*4→14 (int). print() output matches DoD spec exactly.
|
||||
Extra: 9/3→3, 10/4→2.5. Whole-valued division returns int; non-whole returns float.
|
||||
|
||||
### eval/D4: PASS @2026-06-15T06:43Z
|
||||
|
||||
`python calc.py "2+3*4"` → 14 exit:0; (2+3)*4 → 20; 7/2 → 3.5; 4/2 → 2; all exit:0.
|
||||
1/0 → stderr "error: division by zero", exit:1. "1 +" → stderr error, exit:1. No traceback.
|
||||
Adversarial: wrong arg count exits 1 with usage message; lex error (abc) exits 1 with error.
|
||||
|
||||
### eval/D5: PASS @2026-06-15T06:43Z
|
||||
|
||||
`python -m unittest -q` → Ran 46 tests in 0.001s — OK. 0 failures, no regressions.
|
||||
15 lexer + 20 parser + 11 evaluator tests all pass (commit 0fc263d).
|
||||
|
||||
## Adversary findings
|
||||
|
||||
<!-- Defects will be filed here -->
|
||||
@ -0,0 +1,73 @@
|
||||
# REVIEW — Phase lex (Adversary)
|
||||
|
||||
## Verdicts
|
||||
|
||||
### D1: PASS @2026-06-15T06:26:41Z
|
||||
|
||||
Cold run evidence:
|
||||
```
|
||||
NUMBER 42 int ✓ (integer, value==42, isinstance int)
|
||||
NUMBER 3.14 float ✓
|
||||
NUMBER 0.5 float ✓ (leading dot)
|
||||
NUMBER 10.0 float ✓ (trailing dot)
|
||||
```
|
||||
Structure check: `tokenize("42")` → 2 tokens, `[NUMBER(42), EOF]`. Exact match.
|
||||
|
||||
Break-it probes: bare `.` raises `ValueError` (not `LexError`) — see Adversary finding AF-1 below. Not a DoD blocker (bare dot not in spec), logged as defect.
|
||||
|
||||
### D2: PASS @2026-06-15T06:26:41Z
|
||||
|
||||
Cold run evidence:
|
||||
```
|
||||
tokenize("1+2*3") → ['NUMBER', 'PLUS', 'NUMBER', 'STAR', 'NUMBER', 'EOF'] ✓
|
||||
tokenize("+-*/()") → ['PLUS','MINUS','STAR','SLASH','LPAREN','RPAREN','EOF'] ✓
|
||||
```
|
||||
All 6 operator/paren kinds verified.
|
||||
|
||||
### D3: PASS @2026-06-15T06:26:41Z
|
||||
|
||||
Cold run evidence:
|
||||
```
|
||||
tokenize(" 12 + 3 ") → ['NUMBER', 'PLUS', 'NUMBER', 'EOF'] ✓
|
||||
tokenize("1\t+\t2") → ['NUMBER', 'PLUS', 'NUMBER', 'EOF'] ✓
|
||||
tokenize("1 @ 2") → calc.lexer.LexError: unexpected character '@' at position 2 ✓
|
||||
tokenize("abc") → calc.lexer.LexError: unexpected character 'a' at position 0 ✓
|
||||
tokenize("$") → calc.lexer.LexError: unexpected character '$' at position 0 ✓
|
||||
```
|
||||
LexError message contains offending char and its position.
|
||||
|
||||
### D4: PASS @2026-06-15T06:26:41Z
|
||||
|
||||
Cold run evidence:
|
||||
```
|
||||
Ran 15 tests in 0.000s
|
||||
OK
|
||||
```
|
||||
Plan's exact commands:
|
||||
```
|
||||
tokenize('3.5*(1-2)') → [('NUMBER', 3.5), ('STAR', '*'), ('LPAREN', '('), ('NUMBER', 1), ('MINUS', '-'), ('NUMBER', 2), ('RPAREN', ')'), ('EOF', '')] ✓
|
||||
tokenize('1 @ 2') → calc.lexer.LexError: unexpected character '@' at position 2 ✓
|
||||
```
|
||||
Tests cover D1–D3 including all plan-required cases: `" 12 + 3 "`, `"3.5*(1-2)"`, `"1 @ 2"`.
|
||||
|
||||
---
|
||||
|
||||
## Adversary Findings
|
||||
|
||||
### AF-1 (non-blocking): bare `.` leaks `ValueError` instead of `LexError`
|
||||
|
||||
**Repro:** `python -c "from calc.lexer import tokenize; tokenize('.')"`
|
||||
|
||||
**Actual:** `ValueError: could not convert string to float: '.'`
|
||||
|
||||
**Expected:** `LexError` (or at minimum, not a raw `ValueError` from Python internals)
|
||||
|
||||
**Impact:** The DoD does not list bare dot as a required error case. Not blocking DONE, but future parser phases may hit this if they ever pass a stray `.` to the lexer. Recommend wrapping in a try/except and re-raising as LexError.
|
||||
|
||||
**Status:** OPEN (non-blocking)
|
||||
|
||||
---
|
||||
|
||||
## No VETO
|
||||
|
||||
All DoD gates (D1, D2, D3, D4) verified PASS. Builder may write `## DONE` to STATUS-lex.md.
|
||||
@ -0,0 +1,113 @@
|
||||
# REVIEW — Phase parse (Adversary)
|
||||
|
||||
## Status
|
||||
All gates D1–D6 verified PASS @2026-06-15T06:32:30Z.
|
||||
|
||||
---
|
||||
|
||||
## Verdicts
|
||||
|
||||
### parse/D1: PASS @2026-06-15T06:32:30Z
|
||||
|
||||
Cold re-run of Builder's claimed commands:
|
||||
```
|
||||
BinOp('+', Num(1), BinOp('*', Num(2), Num(3))) # 1+2*3 — * binds tighter ✓
|
||||
BinOp('+', BinOp('*', Num(2), Num(3)), Num(4)) # 2*3+4 — * evaluated first ✓
|
||||
```
|
||||
|
||||
Independent break-it probes:
|
||||
- `1+2*3+4` → `BinOp('+', BinOp('+', Num(1), BinOp('*', Num(2), Num(3))), Num(4))` ✓
|
||||
- `4*5-6/2` → `BinOp('-', BinOp('*', Num(4), Num(5)), BinOp('/', Num(6), Num(2)))` ✓
|
||||
|
||||
Derivation confirmed: `_expr` loops over `+/-` consuming `_term` results; `_term` loops over `*//` consuming `_unary` results — two-level precedence grammar is structurally correct.
|
||||
|
||||
---
|
||||
|
||||
### parse/D2: PASS @2026-06-15T06:32:30Z
|
||||
|
||||
Cold re-run:
|
||||
```
|
||||
BinOp('-', BinOp('-', Num(8), Num(3)), Num(2)) # 8-3-2 — left-assoc ✓
|
||||
BinOp('/', BinOp('/', Num(8), Num(4)), Num(2)) # 8/4/2 — left-assoc ✓
|
||||
```
|
||||
|
||||
Independent break-it probe:
|
||||
- `1-2+3` → `BinOp('+', BinOp('-', Num(1), Num(2)), Num(3))` ✓ (left-assoc across mixed +/-)
|
||||
|
||||
While-loop accumulation pattern in `_expr` and `_term` correctly produces left-leaning trees.
|
||||
|
||||
---
|
||||
|
||||
### parse/D3: PASS @2026-06-15T06:32:30Z
|
||||
|
||||
Cold re-run:
|
||||
```
|
||||
BinOp('*', BinOp('+', Num(1), Num(2)), Num(3)) # (1+2)*3 — + under * ✓
|
||||
BinOp('+', Num(2), Num(3)) # ((2+3)) — parens stripped ✓
|
||||
```
|
||||
|
||||
Independent break-it probe:
|
||||
- `3*(1+2)*4` → `BinOp('*', BinOp('*', Num(3), BinOp('+', Num(1), Num(2))), Num(4))` ✓
|
||||
|
||||
`_primary()` correctly enters `_expr()` recursively inside parens and consumes the closing RPAREN.
|
||||
|
||||
---
|
||||
|
||||
### parse/D4: PASS @2026-06-15T06:32:30Z
|
||||
|
||||
Cold re-run:
|
||||
```
|
||||
Unary('-', Num(5)) # -5 ✓
|
||||
Unary('-', BinOp('+', Num(1), Num(2))) # -(1+2) ✓
|
||||
BinOp('*', Num(3), Unary('-', Num(2))) # 3 * -2 ✓
|
||||
Unary('-', Unary('-', Num(5))) # --5 ✓
|
||||
```
|
||||
|
||||
Independent break-it probes:
|
||||
- `1 - -2` → `BinOp('-', Num(1), Unary('-', Num(2)))` ✓
|
||||
- `-1 + -2` → `BinOp('+', Unary('-', Num(1)), Unary('-', Num(2)))` ✓
|
||||
- `-(-(3))` → `Unary('-', Unary('-', Num(3)))` ✓
|
||||
|
||||
`_unary()` is right-recursive (calls itself), so multiple leading negations stack correctly.
|
||||
|
||||
---
|
||||
|
||||
### parse/D5: PASS @2026-06-15T06:32:30Z
|
||||
|
||||
Cold re-run — all 5 plan-required error cases:
|
||||
```
|
||||
OK ParseError: '1 +' -> unexpected end of expression
|
||||
OK ParseError: '(1' -> expected ')' but got 'EOF' ('')
|
||||
OK ParseError: '1 2' -> unexpected token 'NUMBER' (2)
|
||||
OK ParseError: ')(' -> unexpected token 'RPAREN' (')')
|
||||
OK ParseError: '' -> empty expression
|
||||
```
|
||||
|
||||
All raise `ParseError` (not bare `ValueError`, `IndexError`, etc.) ✓
|
||||
|
||||
Independent extra probes:
|
||||
- `'*'` → `ParseError: unexpected token 'STAR' ('*')` ✓
|
||||
- `'((1)'` → `ParseError: expected ')' but got 'EOF' ('')` ✓
|
||||
- `'1++2'` → `ParseError: unexpected token 'PLUS' ('+')` ✓
|
||||
- `'+5'` → `ParseError: unexpected token 'PLUS' ('+')` ✓
|
||||
|
||||
---
|
||||
|
||||
### parse/D6: PASS @2026-06-15T06:32:30Z
|
||||
|
||||
Cold `python -m unittest -q` run:
|
||||
```
|
||||
Ran 35 tests in 0.001s
|
||||
|
||||
OK
|
||||
```
|
||||
|
||||
Test file inspection confirms:
|
||||
- All 20 parser tests use `assertEqual` on dataclass instances (e.g. `BinOp('+', Num(1), BinOp('*', Num(2), Num(3)))`), not on evaluated numeric results ✓
|
||||
- Coverage: D1 (4 tests), D2 (4 tests), D3 (3 tests), D4 (4 tests), D5 (5 tests) ✓
|
||||
|
||||
---
|
||||
|
||||
## Adversary findings
|
||||
|
||||
No findings. All DoD gates pass with no defects detected.
|
||||
@ -0,0 +1,167 @@
|
||||
# STATUS — Phase eval (Builder)
|
||||
|
||||
## DONE
|
||||
|
||||
All gates D1–D5 Adversary-verified PASS @2026-06-15T06:43Z. Phase eval complete.
|
||||
|
||||
## Current State
|
||||
|
||||
Gates D1–D5 implemented, claimed, and Adversary-verified. Phase complete.
|
||||
|
||||
Implementation commit: 0fc263d
|
||||
|
||||
---
|
||||
|
||||
## Gate D1 — arithmetic — CLAIMED, awaiting Adversary
|
||||
|
||||
**WHAT:** `evaluate(parse(tokenize(s)))` is correct for `+ - * /`, precedence, parens, and unary minus.
|
||||
|
||||
**HOW:**
|
||||
```bash
|
||||
python -c "
|
||||
from calc.lexer import tokenize
|
||||
from calc.parser import parse
|
||||
from calc.evaluator import evaluate
|
||||
cases = [('2+3*4', 14), ('(2+3)*4', 20), ('8-3-2', 3), ('-2+5', 3), ('2*-3', -6)]
|
||||
for expr, expected in cases:
|
||||
result = evaluate(parse(tokenize(expr)))
|
||||
status = 'OK' if result == expected else f'FAIL (got {result!r})'
|
||||
print(f'{status}: {expr!r} -> {result}')
|
||||
"
|
||||
```
|
||||
|
||||
**EXPECTED:**
|
||||
```
|
||||
OK: '2+3*4' -> 14
|
||||
OK: '(2+3)*4' -> 20
|
||||
OK: '8-3-2' -> 3
|
||||
OK: '-2+5' -> 3
|
||||
OK: '2*-3' -> -6
|
||||
```
|
||||
|
||||
**WHERE:** `calc/evaluator.py` — commit 0fc263d
|
||||
|
||||
---
|
||||
|
||||
## Gate D2 — division — CLAIMED, awaiting Adversary
|
||||
|
||||
**WHAT:** `/` is true division (`"7/2"` → 3.5). Division by zero raises `EvalError`, not bare `ZeroDivisionError`.
|
||||
|
||||
**HOW:**
|
||||
```bash
|
||||
python -c "
|
||||
from calc.lexer import tokenize
|
||||
from calc.parser import parse
|
||||
from calc.evaluator import evaluate, EvalError
|
||||
|
||||
print(evaluate(parse(tokenize('7/2'))))
|
||||
|
||||
try:
|
||||
evaluate(parse(tokenize('1/0')))
|
||||
print('FAIL: no exception')
|
||||
except EvalError as e:
|
||||
print(f'OK EvalError: {e}')
|
||||
except ZeroDivisionError:
|
||||
print('FAIL: bare ZeroDivisionError escaped')
|
||||
"
|
||||
```
|
||||
|
||||
**EXPECTED:**
|
||||
```
|
||||
3.5
|
||||
OK EvalError: division by zero
|
||||
```
|
||||
|
||||
**WHERE:** `calc/evaluator.py` `evaluate()` BinOp '/' branch — commit 0fc263d
|
||||
|
||||
---
|
||||
|
||||
## Gate D3 — result type — CLAIMED, awaiting Adversary
|
||||
|
||||
**WHAT:** Whole-valued results return `int` (no trailing `.0`); non-whole return `float`.
|
||||
|
||||
Rule: after division, if `result.is_integer()`, return `int(result)`. Integer arithmetic stays `int` natively.
|
||||
|
||||
**HOW:**
|
||||
```bash
|
||||
python -c "
|
||||
from calc.lexer import tokenize
|
||||
from calc.parser import parse
|
||||
from calc.evaluator import evaluate
|
||||
|
||||
r1 = evaluate(parse(tokenize('4/2')))
|
||||
r2 = evaluate(parse(tokenize('7/2')))
|
||||
r3 = evaluate(parse(tokenize('2+3*4')))
|
||||
print(repr(r1), type(r1).__name__)
|
||||
print(repr(r2), type(r2).__name__)
|
||||
print(repr(r3), type(r3).__name__)
|
||||
"
|
||||
```
|
||||
|
||||
**EXPECTED:**
|
||||
```
|
||||
2 int
|
||||
3.5 float
|
||||
14 int
|
||||
```
|
||||
|
||||
**WHERE:** `calc/evaluator.py` `evaluate()` BinOp '/' branch — commit 0fc263d
|
||||
|
||||
---
|
||||
|
||||
## Gate D4 — CLI — CLAIMED, awaiting Adversary
|
||||
|
||||
**WHAT:** `python calc.py "2+3*4"` prints `14` and exits 0; invalid input prints error to stderr and exits non-zero (no traceback).
|
||||
|
||||
**HOW:**
|
||||
```bash
|
||||
python calc.py "2+3*4"; echo "exit:$?"
|
||||
python calc.py "(2+3)*4"; echo "exit:$?"
|
||||
python calc.py "7/2"; echo "exit:$?"
|
||||
python calc.py "4/2"; echo "exit:$?"
|
||||
python calc.py "1/0" 2>&1; echo "exit:$?"
|
||||
python calc.py "1 +" 2>&1; echo "exit:$?"
|
||||
```
|
||||
|
||||
**EXPECTED:**
|
||||
```
|
||||
14
|
||||
exit:0
|
||||
20
|
||||
exit:0
|
||||
3.5
|
||||
exit:0
|
||||
2
|
||||
exit:0
|
||||
error: division by zero
|
||||
exit:1
|
||||
error: unexpected end of expression
|
||||
exit:1
|
||||
```
|
||||
|
||||
**WHERE:** `calc.py` — commit 0fc263d
|
||||
|
||||
---
|
||||
|
||||
## Gate D5 — tests green + end-to-end — CLAIMED, awaiting Adversary
|
||||
|
||||
**WHAT:** Full unittest suite passes with 0 failures: 15 lexer + 20 parser + 11 evaluator = 46 tests.
|
||||
|
||||
**HOW:**
|
||||
```bash
|
||||
python -m unittest -q
|
||||
```
|
||||
|
||||
**EXPECTED:**
|
||||
```
|
||||
Ran 46 tests in 0.00Xs
|
||||
|
||||
OK
|
||||
```
|
||||
|
||||
Test coverage in `calc/test_evaluator.py`:
|
||||
- `TestArithmetic` (5 tests): D1 — 2+3*4, (2+3)*4, 8-3-2, -2+5, 2*-3
|
||||
- `TestDivision` (3 tests): D2 — 7/2, 1/0, 5/(2-2)
|
||||
- `TestResultType` (3 tests): D3 — 4/2→int, 7/2→float, 2+3*4→int
|
||||
|
||||
**WHERE:** `calc/test_evaluator.py`, `calc/evaluator.py` — commit 0fc263d
|
||||
@ -0,0 +1,103 @@
|
||||
# STATUS — Phase lex (Builder)
|
||||
|
||||
## DONE
|
||||
|
||||
All gates D1, D2, D3, D4 verified PASS by Adversary @2026-06-15T06:26:41Z.
|
||||
AF-1 fixed: bare `.` now raises `LexError` instead of leaking `ValueError`.
|
||||
|
||||
## Current State
|
||||
|
||||
All implementation complete. All gates PASSED.
|
||||
|
||||
---
|
||||
|
||||
## Gate D1 — numbers — CLAIMED, awaiting Adversary
|
||||
|
||||
**WHAT:** `calc/lexer.py` tokenizes integers and floats to NUMBER tokens with numeric values.
|
||||
|
||||
**HOW:** Run from repo root (where calc/ lives):
|
||||
```bash
|
||||
python -c "from calc.lexer import tokenize; t=tokenize('42'); print(t[0].kind, t[0].value, type(t[0].value).__name__)"
|
||||
python -c "from calc.lexer import tokenize; t=tokenize('3.14'); print(t[0].kind, t[0].value, type(t[0].value).__name__)"
|
||||
python -c "from calc.lexer import tokenize; t=tokenize('.5'); print(t[0].kind, t[0].value, type(t[0].value).__name__)"
|
||||
python -c "from calc.lexer import tokenize; t=tokenize('10.'); print(t[0].kind, t[0].value, type(t[0].value).__name__)"
|
||||
```
|
||||
|
||||
**EXPECTED:**
|
||||
```
|
||||
NUMBER 42 int
|
||||
NUMBER 3.14 float
|
||||
NUMBER 0.5 float
|
||||
NUMBER 10.0 float
|
||||
```
|
||||
(Each followed by EOF as second token.)
|
||||
|
||||
**WHERE:** `calc/lexer.py` — commit to be pushed with this status.
|
||||
|
||||
---
|
||||
|
||||
## Gate D2 — operators & parens — CLAIMED, awaiting Adversary
|
||||
|
||||
**WHAT:** `+ - * / ( )` each tokenize to PLUS, MINUS, STAR, SLASH, LPAREN, RPAREN.
|
||||
|
||||
**HOW:**
|
||||
```bash
|
||||
python -c "from calc.lexer import tokenize; print([t.kind for t in tokenize('1+2*3')])"
|
||||
```
|
||||
|
||||
**EXPECTED:**
|
||||
```
|
||||
['NUMBER', 'PLUS', 'NUMBER', 'STAR', 'NUMBER', 'EOF']
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Gate D3 — whitespace & errors — CLAIMED, awaiting Adversary
|
||||
|
||||
**WHAT:** Spaces/tabs are skipped; invalid chars raise LexError with char and position.
|
||||
|
||||
**HOW:**
|
||||
```bash
|
||||
python -c "from calc.lexer import tokenize; print([t.kind for t in tokenize(' 12 + 3 ')])"
|
||||
python -c "from calc.lexer import tokenize; tokenize('1 @ 2')"
|
||||
```
|
||||
|
||||
**EXPECTED:**
|
||||
```
|
||||
['NUMBER', 'PLUS', 'NUMBER', 'EOF']
|
||||
calc.lexer.LexError: unexpected character '@' at position 2
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Gate D4 — tests green — CLAIMED, awaiting Adversary
|
||||
|
||||
**WHAT:** `calc/test_lexer.py` passes with 0 failures.
|
||||
|
||||
**HOW:**
|
||||
```bash
|
||||
python -m unittest -q
|
||||
```
|
||||
|
||||
**EXPECTED:**
|
||||
```
|
||||
Ran 15 tests in 0.00Xs
|
||||
|
||||
OK
|
||||
```
|
||||
|
||||
Also the plan's exact verification commands:
|
||||
```bash
|
||||
python -c "from calc.lexer import tokenize; print([(t.kind,t.value) for t in tokenize('3.5*(1-2)')])"
|
||||
```
|
||||
**Expected:**
|
||||
```
|
||||
[('NUMBER', 3.5), ('STAR', '*'), ('LPAREN', '('), ('NUMBER', 1), ('MINUS', '-'), ('NUMBER', 2), ('RPAREN', ')'), ('EOF', '')]
|
||||
```
|
||||
|
||||
```bash
|
||||
python -c "from calc.lexer import tokenize; tokenize('1 @ 2')"
|
||||
```
|
||||
**Expected:** raises `calc.lexer.LexError: unexpected character '@' at position 2`
|
||||
|
||||
**WHERE:** `calc/test_lexer.py`, `calc/lexer.py` — see commit sha after push.
|
||||
@ -0,0 +1,157 @@
|
||||
# STATUS — Phase parse (Builder)
|
||||
|
||||
## DONE
|
||||
|
||||
All gates D1–D6 Adversary-verified PASS @2026-06-15T06:32:30Z. Phase parse complete.
|
||||
|
||||
## Current State
|
||||
|
||||
Gates D1 through D6 implemented, claimed, and Adversary-verified. Phase complete.
|
||||
|
||||
---
|
||||
|
||||
## Gate D1 — precedence — CLAIMED, awaiting Adversary
|
||||
|
||||
**WHAT:** `*` and `/` bind tighter than `+` and `-`. `1+2*3` parses as `BinOp('+', Num(1), BinOp('*', Num(2), Num(3)))`, not `BinOp('+', BinOp('+',…))`.
|
||||
|
||||
**HOW:**
|
||||
```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+4')))"
|
||||
```
|
||||
|
||||
**EXPECTED:**
|
||||
```
|
||||
BinOp('+', Num(1), BinOp('*', Num(2), Num(3)))
|
||||
BinOp('+', BinOp('*', Num(2), Num(3)), Num(4))
|
||||
```
|
||||
|
||||
**WHERE:** `calc/parser.py` — commit 64d0252
|
||||
|
||||
---
|
||||
|
||||
## Gate D2 — left associativity — CLAIMED, awaiting Adversary
|
||||
|
||||
**WHAT:** Same-precedence operators associate left. `8-3-2` → `(8-3)-2`; `8/4/2` → `(8/4)/2`.
|
||||
|
||||
**HOW:**
|
||||
```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` — commit 64d0252
|
||||
|
||||
---
|
||||
|
||||
## Gate D3 — parentheses — CLAIMED, awaiting Adversary
|
||||
|
||||
**WHAT:** Parens override precedence. `(1+2)*3` parses with `+` under `*`.
|
||||
|
||||
**HOW:**
|
||||
```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))')))"
|
||||
```
|
||||
|
||||
**EXPECTED:**
|
||||
```
|
||||
BinOp('*', BinOp('+', Num(1), Num(2)), Num(3))
|
||||
BinOp('+', Num(2), Num(3))
|
||||
```
|
||||
|
||||
**WHERE:** `calc/parser.py` `_primary()` method — commit 64d0252
|
||||
|
||||
---
|
||||
|
||||
## Gate D4 — unary minus — CLAIMED, awaiting Adversary
|
||||
|
||||
**WHAT:** Leading and nested unary minus parses correctly: `-5`, `-(1+2)`, `3 * -2`.
|
||||
|
||||
**HOW:**
|
||||
```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')))"
|
||||
python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('--5')))"
|
||||
```
|
||||
|
||||
**EXPECTED:**
|
||||
```
|
||||
Unary('-', Num(5))
|
||||
Unary('-', BinOp('+', Num(1), Num(2)))
|
||||
BinOp('*', Num(3), Unary('-', Num(2)))
|
||||
Unary('-', Unary('-', Num(5)))
|
||||
```
|
||||
|
||||
**WHERE:** `calc/parser.py` `_unary()` method — commit 64d0252
|
||||
|
||||
---
|
||||
|
||||
## Gate D5 — errors — CLAIMED, awaiting Adversary
|
||||
|
||||
**WHAT:** Malformed input raises `ParseError` (not any other exception) for all five plan cases.
|
||||
|
||||
**HOW:**
|
||||
```bash
|
||||
python -c "
|
||||
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: {src!r} did not raise')
|
||||
except ParseError as e:
|
||||
print(f'OK ParseError: {src!r} -> {e}')
|
||||
except Exception as e:
|
||||
print(f'FAIL wrong exc: {src!r} -> {type(e).__name__}: {e}')
|
||||
"
|
||||
```
|
||||
|
||||
**EXPECTED:**
|
||||
```
|
||||
OK ParseError: '1 +' -> unexpected end of expression
|
||||
OK ParseError: '(1' -> expected ')' but got 'EOF' ('')
|
||||
OK ParseError: '1 2' -> unexpected token 'NUMBER' (2)
|
||||
OK ParseError: ')(' -> unexpected token 'RPAREN' (')')
|
||||
OK ParseError: '' -> empty expression
|
||||
```
|
||||
|
||||
**WHERE:** `calc/parser.py` `parse()`, `_primary()`, `_expr()` — commit 64d0252
|
||||
|
||||
---
|
||||
|
||||
## Gate D6 — tests green — CLAIMED, awaiting Adversary
|
||||
|
||||
**WHAT:** `calc/test_parser.py` (unittest) passes with 0 failures, covering D1–D5. Total 35 tests pass (15 lexer + 20 parser).
|
||||
|
||||
**HOW:**
|
||||
```bash
|
||||
python -m unittest -q
|
||||
```
|
||||
|
||||
**EXPECTED:**
|
||||
```
|
||||
Ran 35 tests in 0.00Xs
|
||||
|
||||
OK
|
||||
```
|
||||
|
||||
Test coverage in `calc/test_parser.py`:
|
||||
- `TestPrecedence` (4 tests): D1 — `1+2*3`, `2*3+4`, `6-2*3`, `1+6/2`
|
||||
- `TestAssociativity` (4 tests): D2 — `8-3-2`, `8/4/2`, `1+2+3`, `2*3*4`
|
||||
- `TestParentheses` (3 tests): D3 — `(1+2)*3`, `((2+3))`, `3*(2+1)`
|
||||
- `TestUnaryMinus` (4 tests): D4 — `-5`, `-(1+2)`, `3 * -2`, `--5`
|
||||
- `TestErrors` (5 tests): D5 — `1 +`, `(1`, `1 2`, `)(`, `""`
|
||||
|
||||
All tests assert on tree structure via `assertEqual` on dataclass instances (not on evaluation).
|
||||
|
||||
**WHERE:** `calc/test_parser.py`, `calc/parser.py` — commit 64d0252
|
||||
Reference in New Issue
Block a user