artifacts: add calculators/ — the 30 built calculators (5/variant) + machine-docs + git logs
This commit is contained in:
@ -0,0 +1,7 @@
|
||||
# BACKLOG — eval phase
|
||||
|
||||
## Build backlog
|
||||
<!-- Builder maintains this section -->
|
||||
|
||||
## Adversary findings
|
||||
<!-- Adversary-only section. Items opened here; only Adversary closes them. -->
|
||||
@ -0,0 +1,15 @@
|
||||
# BACKLOG — lex phase
|
||||
|
||||
## Build backlog
|
||||
|
||||
- [x] Implement `calc/lexer.py` with Token, LexError, tokenize()
|
||||
- [x] Implement `calc/test_lexer.py` with full coverage
|
||||
- [x] Claim D1 (numbers)
|
||||
- [x] Claim D2 (operators & parens)
|
||||
- [x] Claim D3 (whitespace & errors)
|
||||
- [x] Claim D4 (tests green)
|
||||
- [ ] Await Adversary PASS on all gates → write ## DONE to STATUS
|
||||
|
||||
## Adversary findings
|
||||
|
||||
<!-- Adversary owns this section -->
|
||||
@ -0,0 +1,16 @@
|
||||
# BACKLOG — parse phase
|
||||
|
||||
## Build backlog
|
||||
|
||||
- [x] Implement `calc/parser.py` with Num, BinOp, Unary nodes and `parse()` function
|
||||
- [x] Implement `calc/test_parser.py` with 24 tests covering D1–D5
|
||||
- [x] Verify D1: precedence (`1+2*3` tree structure confirmed)
|
||||
- [x] Verify D2: left associativity (`8-3-2`, `8/4/2` tree structure confirmed)
|
||||
- [x] Verify D3: parentheses (`(1+2)*3` tree structure confirmed)
|
||||
- [x] Verify D4: unary minus (`-5`, `-(1+2)`, `3*-2` confirmed)
|
||||
- [x] Verify D5: all 5 error cases raise ParseError
|
||||
- [x] Run full test suite: 48/48 pass
|
||||
|
||||
## Adversary findings
|
||||
|
||||
(pending)
|
||||
@ -0,0 +1,12 @@
|
||||
# DECISIONS (append-only, shared)
|
||||
|
||||
## lex phase
|
||||
|
||||
### Token type
|
||||
Used a simple class with `__slots__` for efficiency and `__eq__` for easy test assertions. No namedtuple to allow future extension (e.g. position tracking for later phases).
|
||||
|
||||
### Number regex
|
||||
`r'\d+\.?\d*|\.\d+'` — handles `42`, `3.14`, `10.`, `.5`. The alternation order matters: longest match first via `re.match` at position `i`.
|
||||
|
||||
### EOF token
|
||||
`Token('EOF', None)` — value is None since EOF carries no semantic content; downstream parser phases can type-check on `kind == 'EOF'`.
|
||||
@ -0,0 +1,35 @@
|
||||
# JOURNAL — eval phase
|
||||
|
||||
## Session 1
|
||||
|
||||
Implemented evaluator, CLI, and tests in one pass.
|
||||
|
||||
### evaluator.py design
|
||||
|
||||
`evaluate(node)` recursively walks `Num`, `BinOp`, `Unary` nodes. `EvalError` wraps division by zero so no bare `ZeroDivisionError` escapes the API. `_normalize(value)` converts whole-valued floats to int (satisfying D3).
|
||||
|
||||
### Test run
|
||||
|
||||
```
|
||||
$ python -m unittest -q
|
||||
Ran 69 tests in 0.113s
|
||||
OK
|
||||
```
|
||||
|
||||
Prior suite had 48 tests (lex+parse); 21 new tests from `test_evaluator.py`.
|
||||
|
||||
### CLI manual check
|
||||
|
||||
```
|
||||
$ 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 "8-3-2" → 3
|
||||
$ python calc.py "-2+5" → 3
|
||||
$ python calc.py "2*-3" → -6
|
||||
$ python calc.py "1/0" → error: division by zero (exit 1)
|
||||
$ python calc.py "1 +" → error: unexpected token 'EOF' (exit 1)
|
||||
```
|
||||
|
||||
All match plan-specified expected values.
|
||||
@ -0,0 +1,29 @@
|
||||
# JOURNAL — lex phase (Builder)
|
||||
|
||||
## Session 1
|
||||
|
||||
### Implementation
|
||||
Built `calc/lexer.py` with:
|
||||
- `Token` class with `__slots__ = ('kind', 'value')` and `__eq__` for test assertions
|
||||
- `LexError` exception
|
||||
- `_NUMBER_RE = re.compile(r'\d+\.?\d*|\.\d+')` — matches integers, floats with/without leading dot
|
||||
- `_SINGLE` dict mapping `+ - * / ( )` to kind strings
|
||||
- `tokenize(src)`: iterates, skips whitespace, checks single-char ops, then tries number regex, raises LexError on unknown char; appends EOF at end
|
||||
|
||||
### Test run
|
||||
```
|
||||
$ python -m unittest -q
|
||||
Ran 24 tests in 0.000s
|
||||
OK
|
||||
```
|
||||
|
||||
### Verification commands
|
||||
```
|
||||
$ python -c "from calc.lexer import tokenize; print([(t.kind,t.value) for t in tokenize('3.5*(1-2)')])"
|
||||
[('NUMBER', 3.5), ('STAR', '*'), ('LPAREN', '('), ('NUMBER', 1), ('MINUS', '-'), ('NUMBER', 2), ('RPAREN', ')'), ('EOF', None)]
|
||||
|
||||
$ python -c "from calc.lexer import tokenize; tokenize('1 @ 2')"
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
calc.lexer.LexError: unexpected character '@' at position 2
|
||||
```
|
||||
@ -0,0 +1,66 @@
|
||||
# JOURNAL — parse phase (Builder)
|
||||
|
||||
## 2026-06-15 — Initial implementation
|
||||
|
||||
**Design decisions:**
|
||||
|
||||
Grammar used (standard precedence climbing via two layers):
|
||||
```
|
||||
expr := term (('+' | '-') term)*
|
||||
term := factor (('*' | '/') factor)*
|
||||
factor := '-' factor | primary
|
||||
primary := NUMBER | '(' expr ')'
|
||||
```
|
||||
|
||||
Left associativity falls out of the `while` loop pattern in `_expr()` and `_term()` — each new BinOp wraps the accumulated left node.
|
||||
|
||||
Unary minus in `_factor()` recurses to itself, so `--5` → `Unary('-', Unary('-', Num(5)))` correctly. The placement in `_factor()` (between `_term()` and `_primary()`) means `3 * -2` works: `_term()` calls `_factor()` for the right operand, which detects the MINUS.
|
||||
|
||||
**Verification runs:**
|
||||
|
||||
D1:
|
||||
```
|
||||
$ python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('1+2*3')))"
|
||||
BinOp('+', Num(1), BinOp('*', Num(2), Num(3)))
|
||||
```
|
||||
|
||||
D2:
|
||||
```
|
||||
$ python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('8-3-2')))"
|
||||
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')))"
|
||||
BinOp('/', BinOp('/', Num(8), Num(4)), Num(2))
|
||||
```
|
||||
|
||||
D3:
|
||||
```
|
||||
$ python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('(1+2)*3')))"
|
||||
BinOp('*', BinOp('+', Num(1), Num(2)), Num(3))
|
||||
```
|
||||
|
||||
D4:
|
||||
```
|
||||
$ python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('-5')))"
|
||||
Unary('-', Num(5))
|
||||
$ python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('-(1+2)')))"
|
||||
Unary('-', BinOp('+', Num(1), Num(2)))
|
||||
$ python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('3 * -2')))"
|
||||
BinOp('*', Num(3), Unary('-', Num(2)))
|
||||
```
|
||||
|
||||
D5 (all 5 required error cases + extras):
|
||||
```
|
||||
OK: '1 +' -> ParseError: unexpected token 'EOF'
|
||||
OK: '(1' -> ParseError: unclosed parenthesis, got 'EOF'
|
||||
OK: '1 2' -> ParseError: unexpected token 'NUMBER'
|
||||
OK: ')(' -> ParseError: unexpected token 'RPAREN'
|
||||
OK: '' -> ParseError: empty input
|
||||
```
|
||||
|
||||
D6:
|
||||
```
|
||||
$ python -m unittest -q
|
||||
----------------------------------------------------------------------
|
||||
Ran 48 tests in 0.002s
|
||||
OK
|
||||
```
|
||||
@ -0,0 +1,71 @@
|
||||
# REVIEW — eval phase
|
||||
|
||||
Adversary cold-verification log. Each gate gets its own independent pass.
|
||||
|
||||
---
|
||||
|
||||
## eval/D1: PASS @2026-06-15T06:19:58Z
|
||||
|
||||
**Command:** `python -m unittest calc.test_evaluator.TestArithmetic -v`
|
||||
**Result:** 8 tests, 0 failures.
|
||||
|
||||
**Spot-check** (all from CLI):
|
||||
- `2+3*4` → `14` ✓
|
||||
- `(2+3)*4` → `20` ✓
|
||||
- `8-3-2` → `3` ✓
|
||||
- `-2+5` → `3` ✓
|
||||
- `2*-3` → `-6` ✓
|
||||
|
||||
**Break-it probes:** Associativity, precedence, and unary minus all checked. No defects found.
|
||||
|
||||
---
|
||||
|
||||
## eval/D2: PASS @2026-06-15T06:19:58Z
|
||||
|
||||
**Command:** `python -m unittest calc.test_evaluator.TestDivision -v`
|
||||
**Result:** 3 tests, 0 failures.
|
||||
|
||||
**Spot-check:**
|
||||
- `7/2` → `3.5` ✓
|
||||
- `1/0` → error to stderr, exit 1 ✓
|
||||
|
||||
**Break-it probes:** Confirmed `EvalError` is raised (not bare `ZeroDivisionError`). Cold Python assertion test confirms `except EvalError` catches it and `except ZeroDivisionError` does not.
|
||||
|
||||
---
|
||||
|
||||
## eval/D3: PASS @2026-06-15T06:19:58Z
|
||||
|
||||
**Command:** `python -m unittest calc.test_evaluator.TestResultType -v`
|
||||
**Result:** 4 tests, 0 failures.
|
||||
|
||||
**Spot-check:**
|
||||
- `4/2` → `2` (int, no `.0`) ✓
|
||||
- `7/2` → `3.5` (float) ✓
|
||||
|
||||
**Break-it probes:** `isinstance` checks confirm `4/2` returns `int`, `7/2` returns `float`. `_normalize()` correctly converts whole-valued floats to int.
|
||||
|
||||
---
|
||||
|
||||
## eval/D4: PASS @2026-06-15T06:19:58Z
|
||||
|
||||
**Command:** `python -m unittest calc.test_evaluator.TestCLI -v`
|
||||
**Result:** 6 tests, 0 failures.
|
||||
|
||||
**Spot-check:**
|
||||
- `python calc.py "2+3*4"` → stdout `14`, exit 0 ✓
|
||||
- `python calc.py "1 +"` → error to stderr only (stdout empty), exit 1 ✓
|
||||
- `python calc.py "1/0"` → error to stderr only, exit 1 ✓
|
||||
- No tracebacks on any error input (verified with `""`, `"1+"`, `"++1"`, `"1 2"`)
|
||||
|
||||
---
|
||||
|
||||
## eval/D5: PASS @2026-06-15T06:19:58Z
|
||||
|
||||
**Command:** `python -m unittest -q`
|
||||
**Result:** 69 tests, 0 failures (~0.12s). All prior phases (lex + parse) still pass. No regression.
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
All 5 eval gates PASS. No veto. No defects found.
|
||||
@ -0,0 +1,60 @@
|
||||
# REVIEW — lex phase (Adversary)
|
||||
|
||||
## Status
|
||||
All four gates verified PASS. No defects found.
|
||||
|
||||
## Gate verdicts
|
||||
|
||||
### lex/D1: PASS @2026-06-15T06:09:05Z
|
||||
**Evidence:**
|
||||
```
|
||||
NUMBER 42 int
|
||||
NUMBER 3.14 float
|
||||
NUMBER 0.5 float
|
||||
NUMBER 10.0 float
|
||||
```
|
||||
- `tokenize("42")` → `[Token(NUMBER, 42), Token(EOF, None)]` — int type confirmed
|
||||
- All float variants (`3.14`, `.5`, `10.`) produce float values
|
||||
- Regex `r'\d+\.?\d*|\.\d+'` correctly handles all cases
|
||||
|
||||
### lex/D2: PASS @2026-06-15T06:09:05Z
|
||||
**Evidence:**
|
||||
```
|
||||
['NUMBER', 'PLUS', 'NUMBER', 'STAR', 'NUMBER', 'EOF']
|
||||
['PLUS', 'MINUS', 'STAR', 'SLASH', 'LPAREN', 'RPAREN', 'EOF']
|
||||
```
|
||||
- `tokenize("1+2*3")` → correct 6-token sequence
|
||||
- All six operators `+-*/()` map to correct kinds
|
||||
|
||||
### lex/D3: PASS @2026-06-15T06:09:05Z
|
||||
**Evidence:**
|
||||
- `tokenize(" 12 + 3 ")` → `['NUMBER', 'PLUS', 'NUMBER', 'EOF']` — spaces skipped
|
||||
- `tokenize("1\t+\t2")` → `['NUMBER', 'PLUS', 'NUMBER', 'EOF']` — tabs skipped
|
||||
- `tokenize("1 @ 2")` raises `calc.lexer.LexError: unexpected character '@' at position 2`
|
||||
- LexError message contains both the char (`@`) and position (`2`) ✓
|
||||
- Letters (`abc`) and `$` also raise LexError ✓
|
||||
|
||||
### lex/D4: PASS @2026-06-15T06:09:05Z
|
||||
**Evidence:**
|
||||
```
|
||||
----------------------------------------------------------------------
|
||||
Ran 24 tests in 0.000s
|
||||
|
||||
OK
|
||||
```
|
||||
`python -m unittest -q` — 24 tests, 0 failures, 0 errors
|
||||
|
||||
## Probes run (independent / adversarial)
|
||||
|
||||
All probes ran from cold start in Adversary's own clone.
|
||||
|
||||
- `tokenize('')` → `['EOF']` — empty string handled ✓
|
||||
- `tokenize('10.+.5')` → `[(NUMBER, 10.0), (PLUS, +), (NUMBER, 0.5), EOF]` — consecutive floats ✓
|
||||
- `tokenize('-3')` → `[(MINUS, -), (NUMBER, 3), EOF]` — unary minus handled as operator ✓
|
||||
- `tokenize('((1))')` → `[LPAREN, LPAREN, NUMBER, RPAREN, RPAREN, EOF]` — nested parens ✓
|
||||
- `tokenize('abc')` raises LexError at position 0 ✓
|
||||
- `tokenize('$')` raises LexError at position 0 ✓
|
||||
- Plan's own verification command confirmed:
|
||||
`tokenize('3.5*(1-2)')` → `[('NUMBER', 3.5), ('STAR', '*'), ('LPAREN', '('), ('NUMBER', 1), ('MINUS', '-'), ('NUMBER', 2), ('RPAREN', ')'), ('EOF', None)]` ✓
|
||||
|
||||
No defects found. No veto.
|
||||
@ -0,0 +1,106 @@
|
||||
# REVIEW — parse phase (Adversary)
|
||||
|
||||
## Status
|
||||
All six gates verified PASS. No defects found. No veto.
|
||||
|
||||
## Gate verdicts
|
||||
|
||||
### parse/D1: PASS @2026-06-15T06:13:00Z
|
||||
**Evidence (cold run):**
|
||||
```
|
||||
BinOp('+', Num(1), BinOp('*', Num(2), Num(3))) # 1+2*3
|
||||
BinOp('+', BinOp('*', Num(2), Num(3)), Num(1)) # 2*3+1
|
||||
BinOp('-', Num(5), BinOp('*', Num(2), Num(3))) # 5-2*3
|
||||
```
|
||||
- `*`/`/` bind tighter than `+`/`-` in all three forms ✓
|
||||
- `_term()` loop handles `STAR`/`SLASH` before `_expr()` handles `PLUS`/`MINUS`
|
||||
|
||||
**Adversarial probes (all correct):**
|
||||
- `1+6/2` → `BinOp('+', Num(1), BinOp('/', Num(6), Num(2)))` ✓
|
||||
- `2*3+4*5` → `BinOp('+', BinOp('*', Num(2), Num(3)), BinOp('*', Num(4), Num(5)))` ✓
|
||||
- `10-2*3+1` → `BinOp('+', BinOp('-', Num(10), BinOp('*', Num(2), Num(3))), Num(1))` ✓
|
||||
|
||||
---
|
||||
|
||||
### parse/D2: PASS @2026-06-15T06:13:00Z
|
||||
**Evidence (cold run):**
|
||||
```
|
||||
BinOp('-', BinOp('-', Num(8), Num(3)), Num(2)) # 8-3-2
|
||||
BinOp('/', BinOp('/', Num(8), Num(4)), Num(2)) # 8/4/2
|
||||
```
|
||||
- Both levels associate left via `while` loops in `_expr()` and `_term()` ✓
|
||||
|
||||
**Adversarial probes (all correct):**
|
||||
- `1+2+3` → `BinOp('+', BinOp('+', Num(1), Num(2)), Num(3))` ✓
|
||||
- `1-2+3` → `BinOp('+', BinOp('-', Num(1), Num(2)), Num(3))` ✓
|
||||
|
||||
---
|
||||
|
||||
### parse/D3: PASS @2026-06-15T06:13:00Z
|
||||
**Evidence (cold run):**
|
||||
```
|
||||
BinOp('*', BinOp('+', Num(1), Num(2)), Num(3)) # (1+2)*3
|
||||
BinOp('*', Num(1), BinOp('+', Num(2), Num(3))) # 1*(2+3)
|
||||
```
|
||||
- `_primary()` calls `_expr()` recursively on paren contents ✓
|
||||
|
||||
**Adversarial probes (all correct):**
|
||||
- `((1+2))*3` → `BinOp('*', BinOp('+', Num(1), Num(2)), Num(3))` ✓
|
||||
- `(2*(3+4))` → `BinOp('*', Num(2), BinOp('+', Num(3), Num(4)))` ✓
|
||||
|
||||
---
|
||||
|
||||
### parse/D4: PASS @2026-06-15T06:13:00Z
|
||||
**Evidence (cold run):**
|
||||
```
|
||||
Unary('-', Num(5)) # -5
|
||||
Unary('-', BinOp('+', Num(1), Num(2))) # -(1+2)
|
||||
BinOp('*', Num(3), Unary('-', Num(2))) # 3 * -2
|
||||
```
|
||||
- `_factor()` recurses for unary minus; `_term()` calls `_factor()` so unary applies at factor level ✓
|
||||
|
||||
**Adversarial probes (all correct):**
|
||||
- `--5` → `Unary('-', Unary('-', Num(5)))` ✓ (double unary via recursion)
|
||||
- `-(2*3)` → `Unary('-', BinOp('*', Num(2), Num(3)))` ✓
|
||||
- `1 - -2` → `BinOp('-', Num(1), Unary('-', Num(2)))` ✓ (binary then unary)
|
||||
- `-1 + 2` → `BinOp('+', Unary('-', Num(1)), Num(2))` ✓
|
||||
|
||||
---
|
||||
|
||||
### parse/D5: PASS @2026-06-15T06:13:00Z
|
||||
**Evidence (cold run) — all five required cases raise ParseError, not LexError/IndexError/etc.:**
|
||||
```
|
||||
OK: '1 +' -> ParseError: unexpected token 'EOF'
|
||||
OK: '(1' -> ParseError: unclosed parenthesis, got 'EOF'
|
||||
OK: '1 2' -> ParseError: unexpected token 'NUMBER'
|
||||
OK: ')(' -> ParseError: unexpected token 'RPAREN'
|
||||
OK: '' -> ParseError: empty input
|
||||
```
|
||||
|
||||
**Adversarial error probes (all raise ParseError cleanly):**
|
||||
- `'5 *'` → ParseError: unexpected token 'EOF' ✓
|
||||
- `'()'` → ParseError: unexpected token 'RPAREN' ✓
|
||||
- `'+5'` → ParseError: unexpected token 'PLUS' ✓ (unary plus unsupported)
|
||||
- `'1++2'` → ParseError: unexpected token 'PLUS' ✓
|
||||
- `'1 2 3'` → ParseError: unexpected token 'NUMBER' ✓
|
||||
- `'((1)'` → ParseError: unclosed parenthesis, got 'EOF' ✓
|
||||
|
||||
---
|
||||
|
||||
### parse/D6: PASS @2026-06-15T06:13:00Z
|
||||
**Evidence (cold run):**
|
||||
```
|
||||
----------------------------------------------------------------------
|
||||
Ran 48 tests in 0.001s
|
||||
|
||||
OK
|
||||
```
|
||||
`python -m unittest -q` — 48 tests (24 parser + 24 lexer), 0 failures, 0 errors ✓
|
||||
|
||||
---
|
||||
|
||||
## Probes run (independent / adversarial)
|
||||
|
||||
All probes ran from cold start in Adversary's own clone (work-adv/).
|
||||
|
||||
No defects found. No veto.
|
||||
@ -0,0 +1,97 @@
|
||||
# STATUS — eval phase
|
||||
|
||||
## DONE
|
||||
|
||||
All gates D1–D5 Adversary-verified PASS @2026-06-15T06:19:58Z. No veto. Sequence complete.
|
||||
|
||||
## Current state
|
||||
All gates D1–D5 implemented, claimed, and Adversary-verified.
|
||||
|
||||
## Gate D1 — CLAIMED, awaiting Adversary
|
||||
|
||||
**WHAT:** `evaluate(parse(tokenize(s)))` correct for +, -, *, /, precedence, parens, unary minus.
|
||||
|
||||
**HOW:** Run from repo root:
|
||||
```bash
|
||||
python -m unittest calc.test_evaluator.TestArithmetic -v
|
||||
```
|
||||
|
||||
**EXPECTED:** 8 tests pass, 0 failures.
|
||||
|
||||
**WHERE:** `calc/evaluator.py`, `calc/test_evaluator.py` @ HEAD
|
||||
|
||||
Spot-check direct values:
|
||||
```
|
||||
python calc.py "2+3*4" # 14
|
||||
python calc.py "(2+3)*4" # 20
|
||||
python calc.py "8-3-2" # 3
|
||||
python calc.py "-2+5" # 3
|
||||
python calc.py "2*-3" # -6
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Gate D2 — CLAIMED, awaiting Adversary
|
||||
|
||||
**WHAT:** `/` is true division; division by zero raises `EvalError`, not bare `ZeroDivisionError`.
|
||||
|
||||
**HOW:**
|
||||
```bash
|
||||
python -m unittest calc.test_evaluator.TestDivision -v
|
||||
python calc.py "7/2" # 3.5
|
||||
python calc.py "1/0" # error to stderr, exit 1
|
||||
```
|
||||
|
||||
**EXPECTED:** 3 tests pass; `7/2` → `3.5`; `1/0` → stderr + exit 1.
|
||||
|
||||
**WHERE:** `calc/evaluator.py` lines 30–34, `calc/test_evaluator.py` class `TestDivision`.
|
||||
|
||||
---
|
||||
|
||||
## Gate D3 — CLAIMED, awaiting Adversary
|
||||
|
||||
**WHAT:** Whole-valued results → int (no `.0`); fractional → float.
|
||||
|
||||
**HOW:**
|
||||
```bash
|
||||
python -m unittest calc.test_evaluator.TestResultType -v
|
||||
python calc.py "4/2" # 2
|
||||
python calc.py "7/2" # 3.5
|
||||
```
|
||||
|
||||
**EXPECTED:** 4 tests pass; `4/2` → `2`; `7/2` → `3.5`.
|
||||
|
||||
**WHERE:** `_normalize()` in `calc/evaluator.py` lines 14–17; class `TestResultType` in `calc/test_evaluator.py`.
|
||||
|
||||
---
|
||||
|
||||
## Gate D4 — CLAIMED, awaiting Adversary
|
||||
|
||||
**WHAT:** `python calc.py "2+3*4"` prints `14`, exits 0; invalid expr prints to stderr and exits nonzero.
|
||||
|
||||
**HOW:**
|
||||
```bash
|
||||
python calc.py "2+3*4" # stdout: 14, exit 0
|
||||
python calc.py "1 +" # stderr: error message, exit 1
|
||||
python calc.py "1/0" # stderr: error message, exit 1
|
||||
python -m unittest calc.test_evaluator.TestCLI -v
|
||||
```
|
||||
|
||||
**EXPECTED:** 6 CLI tests pass; outputs as stated above.
|
||||
|
||||
**WHERE:** `calc.py` (repo root).
|
||||
|
||||
---
|
||||
|
||||
## Gate D5 — CLAIMED, awaiting Adversary
|
||||
|
||||
**WHAT:** Full suite (lex + parse + eval) passes with 0 failures; prior phases not regressed.
|
||||
|
||||
**HOW:**
|
||||
```bash
|
||||
python -m unittest -q
|
||||
```
|
||||
|
||||
**EXPECTED:** 69 tests in ~0.1s, OK, 0 failures.
|
||||
|
||||
**WHERE:** `calc/test_lexer.py`, `calc/test_parser.py`, `calc/test_evaluator.py`.
|
||||
@ -0,0 +1,86 @@
|
||||
# STATUS — lex phase (Builder)
|
||||
|
||||
## DONE
|
||||
|
||||
All DoD gates (D1–D4) verified PASS by Adversary @2026-06-15T06:09:05Z. No veto. Phase complete.
|
||||
|
||||
## Gates
|
||||
|
||||
### D1 — numbers: **CLAIMED** — awaiting Adversary
|
||||
**WHAT:** `tokenize("42")` → `[Token(NUMBER, 42), Token(EOF, None)]`; floats `3.14`, `.5`, `10.` each produce one NUMBER token with numeric value (int or float).
|
||||
|
||||
**HOW to verify:**
|
||||
```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
|
||||
```
|
||||
|
||||
**WHERE:** `calc/lexer.py` — `tokenize()` function
|
||||
|
||||
---
|
||||
|
||||
### D2 — operators & parens: **CLAIMED** — awaiting Adversary
|
||||
**WHAT:** `+ - * / ( )` tokenize to `PLUS MINUS STAR SLASH LPAREN RPAREN`; `tokenize("1+2*3")` → `NUMBER PLUS NUMBER STAR NUMBER EOF`
|
||||
|
||||
**HOW to verify:**
|
||||
```bash
|
||||
python -c "from calc.lexer import tokenize; print([t.kind for t in tokenize('1+2*3')])"
|
||||
python -c "from calc.lexer import tokenize; print([t.kind for t in tokenize('+-*/()')] )"
|
||||
```
|
||||
|
||||
**EXPECTED:**
|
||||
```
|
||||
['NUMBER', 'PLUS', 'NUMBER', 'STAR', 'NUMBER', 'EOF']
|
||||
['PLUS', 'MINUS', 'STAR', 'SLASH', 'LPAREN', 'RPAREN', 'EOF']
|
||||
```
|
||||
|
||||
**WHERE:** `calc/lexer.py` — `_SINGLE` dict and `tokenize()`
|
||||
|
||||
---
|
||||
|
||||
### D3 — whitespace & errors: **CLAIMED** — awaiting Adversary
|
||||
**WHAT:** Spaces/tabs skipped; invalid chars raise `LexError` with offending char and position.
|
||||
|
||||
**HOW to verify:**
|
||||
```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:**
|
||||
- First command: `['NUMBER', 'PLUS', 'NUMBER', 'EOF']`
|
||||
- Second command: raises `calc.lexer.LexError: unexpected character '@' at position 2`
|
||||
|
||||
**WHERE:** `calc/lexer.py` — whitespace skip + `LexError` raise
|
||||
|
||||
---
|
||||
|
||||
### D4 — tests green: **CLAIMED** — awaiting Adversary
|
||||
**WHAT:** `calc/test_lexer.py` runs 24 tests, 0 failures under `python -m unittest`
|
||||
|
||||
**HOW to verify:**
|
||||
```bash
|
||||
python -m unittest -q
|
||||
```
|
||||
|
||||
**EXPECTED:**
|
||||
```
|
||||
----------------------------------------------------------------------
|
||||
Ran 24 tests in 0.000s
|
||||
|
||||
OK
|
||||
```
|
||||
|
||||
**WHERE:** `calc/test_lexer.py`
|
||||
|
||||
---
|
||||
@ -0,0 +1,159 @@
|
||||
# STATUS — parse phase (Builder)
|
||||
|
||||
## DONE
|
||||
|
||||
All DoD gates (D1–D6) verified PASS by Adversary @2026-06-15T06:13:00Z. No veto. Phase complete.
|
||||
|
||||
## Current state
|
||||
Implementation complete. `calc/parser.py` and `calc/test_parser.py` created. 48 tests pass (24 lex + 24 parser). All gates claimed and Adversary-verified.
|
||||
|
||||
## AST Shape Reference
|
||||
|
||||
```
|
||||
Num(value) — value is int or float
|
||||
BinOp(op, left, right) — op in ('+', '-', '*', '/')
|
||||
Unary(op, operand) — op is '-'
|
||||
```
|
||||
|
||||
`repr()` of each node class is canonical (used in test assertions and verification commands).
|
||||
|
||||
---
|
||||
|
||||
## Gates
|
||||
|
||||
### D1 — precedence: **CLAIMED**, awaiting Adversary
|
||||
|
||||
**WHAT:** `*` and `/` bind tighter than `+` and `-`. `1+2*3` → `BinOp('+', Num(1), BinOp('*', Num(2), Num(3)))` (not `BinOp('*', BinOp('+', ...), ...)`).
|
||||
|
||||
**HOW to verify:**
|
||||
```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+1')))"
|
||||
python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('5-2*3')))"
|
||||
```
|
||||
|
||||
**EXPECTED:**
|
||||
```
|
||||
BinOp('+', Num(1), BinOp('*', Num(2), Num(3)))
|
||||
BinOp('+', BinOp('*', Num(2), Num(3)), Num(1))
|
||||
BinOp('-', Num(5), BinOp('*', Num(2), Num(3)))
|
||||
```
|
||||
|
||||
**WHERE:** `calc/parser.py` — `_expr()` calls `_term()` which handles `*`/`/`; `_expr()` handles `+`/`-` at lower priority.
|
||||
|
||||
---
|
||||
|
||||
### D2 — left associativity: **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 to verify:**
|
||||
```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` — `_expr()` and `_term()` use `while` loops, wrapping the accumulating left side.
|
||||
|
||||
---
|
||||
|
||||
### D3 — parentheses: **CLAIMED**, awaiting Adversary
|
||||
|
||||
**WHAT:** Parens override precedence. `(1+2)*3` → `BinOp('*', BinOp('+', Num(1), Num(2)), Num(3))`.
|
||||
|
||||
**HOW to verify:**
|
||||
```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('1*(2+3)')))"
|
||||
```
|
||||
|
||||
**EXPECTED:**
|
||||
```
|
||||
BinOp('*', BinOp('+', Num(1), Num(2)), Num(3))
|
||||
BinOp('*', Num(1), BinOp('+', Num(2), Num(3)))
|
||||
```
|
||||
|
||||
**WHERE:** `calc/parser.py` — `_primary()` handles `LPAREN … RPAREN` by calling `_expr()` recursively.
|
||||
|
||||
---
|
||||
|
||||
### D4 — unary minus: **CLAIMED**, awaiting Adversary
|
||||
|
||||
**WHAT:** Leading and nested unary minus parses correctly. `-5` → `Unary('-', Num(5))`. `-(1+2)` → `Unary('-', BinOp('+', Num(1), Num(2)))`. `3 * -2` → `BinOp('*', Num(3), Unary('-', Num(2)))`.
|
||||
|
||||
**HOW to verify:**
|
||||
```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')))"
|
||||
```
|
||||
|
||||
**EXPECTED:**
|
||||
```
|
||||
Unary('-', Num(5))
|
||||
Unary('-', BinOp('+', Num(1), Num(2)))
|
||||
BinOp('*', Num(3), Unary('-', Num(2)))
|
||||
```
|
||||
|
||||
**WHERE:** `calc/parser.py` — `_factor()` detects `MINUS` and recurses, returning `Unary('-', operand)`.
|
||||
|
||||
---
|
||||
|
||||
### D5 — errors: **CLAIMED**, awaiting Adversary
|
||||
|
||||
**WHAT:** Malformed input raises `ParseError` (not `LexError`, `IndexError`, etc.). Five required cases: `"1 +"`, `"(1"`, `"1 2"`, `")("`, `""`.
|
||||
|
||||
**HOW to verify:**
|
||||
```bash
|
||||
python -c "
|
||||
from calc.lexer import tokenize
|
||||
from calc.parser import parse, ParseError
|
||||
errors = ['1 +', '(1', '1 2', ')(', '']
|
||||
for src in errors:
|
||||
try:
|
||||
parse(tokenize(src))
|
||||
print(f'FAIL: {src!r} did not raise')
|
||||
except ParseError as e:
|
||||
print(f'OK: {src!r} -> ParseError: {e}')
|
||||
except Exception as e:
|
||||
print(f'FAIL: {src!r} raised wrong exception: {type(e).__name__}: {e}')
|
||||
"
|
||||
```
|
||||
|
||||
**EXPECTED:**
|
||||
```
|
||||
OK: '1 +' -> ParseError: unexpected token 'EOF'
|
||||
OK: '(1' -> ParseError: unclosed parenthesis, got 'EOF'
|
||||
OK: '1 2' -> ParseError: unexpected token 'NUMBER'
|
||||
OK: ')(' -> ParseError: unexpected token 'RPAREN'
|
||||
OK: '' -> ParseError: empty input
|
||||
```
|
||||
|
||||
**WHERE:** `calc/parser.py` — `parse()`, `_primary()`, `_primary()` RPAREN check.
|
||||
|
||||
---
|
||||
|
||||
### D6 — tests green: **CLAIMED**, awaiting Adversary
|
||||
|
||||
**WHAT:** `calc/test_parser.py` + `calc/test_lexer.py` combined: 48 tests, 0 failures under `python -m unittest`.
|
||||
|
||||
**HOW to verify:**
|
||||
```bash
|
||||
python -m unittest -q
|
||||
```
|
||||
|
||||
**EXPECTED:**
|
||||
```
|
||||
----------------------------------------------------------------------
|
||||
Ran 48 tests in 0.002s
|
||||
|
||||
OK
|
||||
```
|
||||
|
||||
**WHERE:** `calc/test_parser.py` (24 tests covering D1–D5), `calc/test_lexer.py` (24 tests from lex phase).
|
||||
Reference in New Issue
Block a user