artifacts: add calculators/ — the 30 built calculators (5/variant) + machine-docs + git logs

This commit is contained in:
2026-06-16 15:39:42 +00:00
parent 64bc360fc0
commit bb85aa9f11
728 changed files with 34148 additions and 0 deletions

View File

@ -0,0 +1,9 @@
# BACKLOG — eval phase
## Build backlog
- [x] D1 — arithmetic: +, -, *, /, precedence, parens, unary minus
- [x] D2 — division: true division, EvalError on div-by-zero
- [x] D3 — result type: int for whole values, float otherwise
- [x] D4 — CLI: calc.py prints result or error to stderr with non-zero exit
- [x] D5 — tests green + end-to-end, no regressions

View File

@ -0,0 +1,12 @@
# BACKLOG — Phase `lex`
## Build backlog
- [x] D1 — Implement integer/float tokenization → NUMBER tokens
- [x] D2 — Implement operator/paren tokenization
- [x] D3 — Whitespace skipping + LexError for invalid chars
- [x] D4 — Write and pass unittest suite (14 tests, 0 failures)
- [ ] Await Adversary PASS on D1D4
## Adversary findings
<!-- Adversary writes here -->

View File

@ -0,0 +1,12 @@
# BACKLOG — Phase `parse`
## Build backlog
- [x] Implement `calc/parser.py` with `ParseError`, `Num`, `BinOp`, `Unary`, `parse()`
- [x] Implement `calc/test_parser.py` with 20 tests covering D1D5
- [x] Run `python -m unittest -q` — 34 tests, all pass
- [x] Claim D1D6
## Adversary findings
_(none yet)_

View File

@ -0,0 +1,19 @@
# DECISIONS (append-only, settled design decisions)
## lex phase
**D-LEX-1:** `Token` uses `@dataclass` for equality and repr, making test assertions clean.
**D-LEX-2:** Number parsing scans a contiguous run of digits and dots, then uses `float()` or `int()` based on presence of `.`. Edge cases `.5` and `10.` handled correctly by Python's built-in conversion.
**D-LEX-3:** `LexError` extends `Exception` directly (no custom fields) — message contains char and position as plain text, sufficient for D3.
**D-LEX-4:** EOF token always appended as the final token to signal end-of-input to the parser (future phase).
## parse phase
**D-PARSE-1:** Recursive-descent with three levels — `_expr` (additive), `_term` (multiplicative), `_unary` (prefix minus), `_primary` (atoms/parens). While loops in `_expr`/`_term` give left-associativity; calling `_term` from `_expr` gives `*/` higher precedence than `+-`.
**D-PARSE-2:** `Num`, `BinOp`, `Unary` are `@dataclass`s — equality and repr are free, making structural test assertions clean.
**D-PARSE-3:** `ParseError` extends `Exception` directly. Message contains the unexpected token kind/value, sufficient for D5.

View File

@ -0,0 +1,34 @@
# JOURNAL — eval phase
## Implementation run
Ran all checks locally before commit:
```
$ python -m unittest -q
----------------------------------------------------------------------
Ran 45 tests in 0.001s
OK
$ 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: division by zero
(exit 1)
$ python calc.py "1 +"
error: unexpected end of input
(exit 1)
```
All 45 tests pass (20 from parse phase, 14 from lex phase, 11 from evaluator tests).
### Design notes
- `_coerce()` handles result type: if a float has no fractional part, cast to int.
- Division by zero caught explicitly and re-raised as `EvalError`.
- CLI catches `LexError`, `ParseError`, `EvalError` — no raw tracebacks.

View File

@ -0,0 +1,24 @@
# JOURNAL — Phase `lex`
## Implementation notes
### Design choices
- Used `@dataclass` for `Token` to get `__eq__` and `__repr__` for free — useful in tests.
- Number parsing: scan while char is digit or `.`; if `.` in raw string → `float()`, else `int()`. Handles `42`, `3.14`, `.5`, `10.`.
- Single-char operators: dict lookup for O(1) dispatch.
- LexError message includes both the character (quoted) and its 0-based position.
### Test run (verified locally)
```
$ python -m unittest -q
Ran 14 tests in 0.000s
OK
$ 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
```

View File

@ -0,0 +1,34 @@
# JOURNAL — Phase `parse`
## Session 1 — initial implementation
**Plan:** Recursive-descent parser. Grammar:
```
expr → term (('+' | '-') term)*
term → unary (('*' | '/') unary)*
unary → '-' unary | primary
primary → NUMBER | '(' expr ')'
```
This naturally encodes `*/` tighter than `+-` (D1) and left-associativity via the while loops (D2). Unary minus (D4) handled in `_unary` before `_primary`.
**Test run:**
```
$ python -m unittest -q
----------------------------------------------------------------------
Ran 34 tests in 0.001s
OK
```
**Key assertions verified manually:**
```
1+2*3 → BinOp('+', Num(1), BinOp('*', Num(2), Num(3))) ✓ D1
8-3-2 → BinOp('-', BinOp('-', Num(8), Num(3)), Num(2)) ✓ D2
8/4/2 → BinOp('/', BinOp('/', Num(8), Num(4)), Num(2)) ✓ D2
(1+2)*3 → BinOp('*', BinOp('+', Num(1), Num(2)), Num(3)) ✓ D3
-5 → Unary('-', Num(5)) ✓ D4
-(1+2) → Unary('-', BinOp('+', Num(1), Num(2))) ✓ D4
3 * -2 → BinOp('*', Num(3), Unary('-', Num(2))) ✓ D4
```
Error cases ('1 +', '(1', '1 2', ')(', '') all raise ParseError ✓ D5

View File

@ -0,0 +1,61 @@
# REVIEW — Phase `eval`
Adversary: verified cold at commit `7e18a9b`.
## Gate verdicts
### eval/D1: PASS @2026-06-15T03:58Z
```
$ 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 ✓
```
Also probed: `2+3*4+1`→15, `100/10/2`→5, `--5`→5 (double unary). All correct.
### eval/D2: PASS @2026-06-15T03:58Z
```
$ python calc.py "7/2" → 3.5 ✓ (true division)
$ python calc.py "1/0" → stderr: "error: division by zero", exit 1 ✓
```
Cold API check: `evaluate(parse(tokenize('1/0')))` raises `EvalError`, not `ZeroDivisionError`.
Division by zero is caught before Python's operator is invoked (`right == 0` guard).
### eval/D3: PASS @2026-06-15T03:58Z
```
$ python calc.py "4/2" → 2 ✓ (int, no .0)
$ python calc.py "7/2" → 3.5 ✓ (float)
$ python calc.py "9/3" → 3 ✓
$ python calc.py "1/3" → 0.333... ✓
$ python calc.py "-6/2" → -3 ✓ (negative whole)
$ python calc.py "-7/2" → -3.5 ✓
```
`_coerce()` rule: if `isinstance(val, float) and val == int(val)` return `int(val)` else return as-is. Consistent with documented rule.
### eval/D4: PASS @2026-06-15T03:58Z
```
$ python calc.py "2+3*4" → stdout: 14, exit 0 ✓
$ python calc.py "1 +" → stderr: "error: unexpected end of input", exit 1 ✓
$ python calc.py "1/0" → stderr: "error: division by zero", exit 1 ✓
$ python calc.py "" → stderr: "error: unexpected end of input", exit 1 ✓
```
No traceback on error (1-line output only). Errors routed to stderr only (stdout empty on error).
### eval/D5: PASS @2026-06-15T03:58Z
```
$ python -m unittest -q
Ran 45 tests in 0.001s
OK
```
45 tests: 14 lex + 20 parse + 11 evaluator. 0 failures. No regression in prior phases.
`test_evaluator.py` covers D1D3 (arithmetic, division, result types, EvalError for div-by-zero).
A CLI check (D4) is exercised via the cold verify commands above.
## Adversary findings
_(none — no defects found; no veto)_
## Summary
All five DoD gates D1D5 PASS. No veto. Builder may write "## DONE" to STATUS-eval.md.

View File

@ -0,0 +1,53 @@
# REVIEW — Phase `lex`
Adversary: cold-verification log. One entry per gate per pass.
## Verdicts
### lex/D1: PASS @2026-06-15T03:52Z
Cold-ran from own clone at commit 462ad1f.
```
tokenize("42") → [('NUMBER', 42), ('EOF', None)] ✓ int
tokenize(".5") → [('NUMBER', 0.5), ('EOF', None)] ✓ float
tokenize("10.") → [('NUMBER', 10.0), ('EOF', None)] ✓ float
tokenize("3.14")→ [('NUMBER', 3.14), ('EOF', None)] ✓ float
```
EOF always appended as final token. int/float types correct.
### lex/D2: PASS @2026-06-15T03:52Z
```
tokenize("1+2*3") → ['NUMBER', 'PLUS', 'NUMBER', 'STAR', 'NUMBER', 'EOF'] ✓
tokenize("()+-(*/") → LPAREN RPAREN PLUS MINUS LPAREN STAR SLASH EOF ✓
```
All six operator/paren kinds map correctly.
### lex/D3: PASS @2026-06-15T03:52Z
```
tokenize(" 12 + 3 ") → ['NUMBER', 'PLUS', 'NUMBER', 'EOF'] ✓ spaces skipped
tokenize("1\t+\t2") → ['NUMBER', 'PLUS', 'NUMBER', 'EOF'] ✓ tabs skipped
tokenize("1 @ 2") raises LexError: unexpected character '@' at position 2 ✓
tokenize("hello") raises LexError: unexpected character 'h' at position 0 ✓
tokenize("$10") raises LexError ✓
```
LexError message contains the offending character and position.
### lex/D4: PASS @2026-06-15T03:52Z
```
$ python -m unittest -q
Ran 14 tests in 0.000s
OK
```
14 tests, 0 failures. Plan's canonical verify commands all produce expected output:
- `3.5*(1-2)``[('NUMBER', 3.5), ('STAR', '*'), ('LPAREN', '('), ('NUMBER', 1), ('MINUS', '-'), ('NUMBER', 2), ('RPAREN', ')'), ('EOF', None)]`
- `1 @ 2` → raises LexError
## Adversary findings
### F1 (non-blocking) — malformed number literals leak ValueError instead of LexError
- `tokenize("..")``ValueError: could not convert string to float: '..'`
- `tokenize("1.2.3")``ValueError: could not convert string to float: '1.2.3'`
The number-scanning loop greedily consumes all `[0-9.]` chars, then calls `float()` which throws a raw ValueError. The DoD (D3) only specifies invalid *characters* (@ $ letters) and these cases are not in the test suite, so this does **not** block DONE. Noted for later phases.
## Summary
All four DoD gates PASS. No veto. Builder may write "## DONE" to STATUS-lex.md.

View File

@ -0,0 +1,63 @@
# REVIEW — Phase `parse`
Adversary: cold-verification log. One entry per gate per pass.
## Verdicts
### parse/D1: PASS @2026-06-15T04:03Z
Cold-ran at commit e9a5152.
```
parse(tokenize('1+2*3')) → BinOp('+', Num(1), BinOp('*', Num(2), Num(3))) ✓
```
`*` binds tighter than `+` — tree structure matches plan requirement. Independently verified extra cases:
`1*2+3*4``BinOp('+', BinOp('*', Num(1), Num(2)), BinOp('*', Num(3), Num(4)))`
### parse/D2: PASS @2026-06-15T04:03Z
```
parse(tokenize('8-3-2')) → BinOp('-', BinOp('-', Num(8), Num(3)), Num(2)) ✓
parse(tokenize('8/4/2')) → BinOp('/', BinOp('/', Num(8), Num(4)), Num(2)) ✓
```
Left-associativity confirmed by `while` loop in `_expr`/`_term` (re-read code). Extra probe:
`1+2+3+4``BinOp('+', BinOp('+', BinOp('+', Num(1), Num(2)), Num(3)), Num(4))`
### parse/D3: PASS @2026-06-15T04:03Z
```
parse(tokenize('(1+2)*3')) → BinOp('*', BinOp('+', Num(1), Num(2)), Num(3)) ✓
```
Double paren `((3))``Num(3)`
### parse/D4: PASS @2026-06-15T04:03Z
```
parse(tokenize('-5')) → Unary('-', Num(5)) ✓
parse(tokenize('-(1+2)')) → Unary('-', BinOp('+', Num(1), Num(2))) ✓
parse(tokenize('3 * -2')) → BinOp('*', Num(3), Unary('-', Num(2))) ✓
```
Extra probes: `--5``Unary('-', Unary('-', Num(5)))` ✓; `1+-2``BinOp('+', Num(1), Unary('-', Num(2)))` ✓;
`-1-2``BinOp('-', Unary('-', Num(1)), Num(2))` ✓; `-(-(1))``Unary('-', Unary('-', Num(1)))`
### parse/D5: PASS @2026-06-15T04:03Z
All five plan-mandated malformed inputs raise `ParseError` (not TypeError/ValueError/IndexError):
```
'1 +' → ParseError: unexpected end of input ✓
'(1' → ParseError: expected RPAREN, got 'EOF' (None) ✓
'1 2' → ParseError: unexpected token 'NUMBER' (2) ✓
')(' → ParseError: unexpected token 'RPAREN' (')') ✓
'' → ParseError: unexpected end of input ✓
```
Extra probes all raise ParseError: `* 1`, `1 * `, `()`, `1 + ()`, `1)`, `(1+2` — all correct.
### parse/D6: PASS @2026-06-15T04:03Z
```
$ python -m unittest -q
Ran 34 tests in 0.001s
OK
```
34 tests (14 lex + 20 parse), 0 failures.
## Adversary findings
_(none — no defects found; no veto)_
## Summary
All six DoD gates PASS. No veto. Builder may write "## DONE" to STATUS-parse.md.

View File

@ -0,0 +1,38 @@
# STATUS — eval phase
## DONE
## Gate: D1D5 PASS (Adversary-verified @2026-06-15T03:58Z, commit 7e18a9b)
### What is claimed
All five gates D1D5 are implemented and verified locally.
### Commit
(see latest `claim(D1-D5)` commit on main)
### Artifacts
- `calc/evaluator.py``evaluate(node) -> int | float`, `EvalError`
- `calc/test_evaluator.py` — unittest suite covering D1D3
- `calc.py` — CLI entry point (D4)
### How to verify (cold)
```bash
python -m unittest -q # 45 tests, 0 failures
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
```
### Expected outputs
| command | stdout | stderr | exit |
|---------|--------|--------|------|
| `python calc.py "2+3*4"` | `14` | — | 0 |
| `python calc.py "(2+3)*4"` | `20` | — | 0 |
| `python calc.py "7/2"` | `3.5` | — | 0 |
| `python calc.py "4/2"` | `2` | — | 0 |
| `python calc.py "1/0"` | — | `error: division by zero` | 1 |
| `python calc.py "1 +"` | — | `error: unexpected end of input` | 1 |

View File

@ -0,0 +1,49 @@
# STATUS — Phase `lex`
## DONE
All gates D1D4: PASS (Adversary-verified @2026-06-15T03:52Z, commit c974829).
Note: Adversary F1 (non-blocking) — malformed floats like `".."` leak ValueError; not in DoD scope.
## Current State
Gates D1D4: CLAIMED, awaiting Adversary verification.
## Claims
### D1 — numbers
**WHAT:** Integers and floats tokenize to NUMBER tokens with numeric values.
**HOW:** Run `python -c "from calc.lexer import tokenize; print([(t.kind,t.value) for t in tokenize('42')])"` — expected `[('NUMBER', 42), ('EOF', None)]`. Also `.5` → float 0.5, `10.` → float 10.0, `3.14` → float 3.14.
**EXPECTED:** `[('NUMBER', 42), ('EOF', None)]`
**WHERE:** `calc/lexer.py`, `calc/test_lexer.py`
### D2 — operators & parens
**WHAT:** `+ - * / ( )` each tokenize to PLUS/MINUS/STAR/SLASH/LPAREN/RPAREN; expression `1+2*3` yields NUMBER PLUS NUMBER STAR NUMBER EOF.
**HOW:** `python -c "from calc.lexer import tokenize; print([t.kind for t in tokenize('1+2*3')])"`
**EXPECTED:** `['NUMBER', 'PLUS', 'NUMBER', 'STAR', 'NUMBER', 'EOF']`
**WHERE:** `calc/lexer.py`
### D3 — whitespace & errors
**WHAT:** Spaces/tabs skipped; invalid characters raise LexError with character and position.
**HOW:**
- `python -c "from calc.lexer import tokenize; print([t.kind for t in tokenize(' 12 + 3 ')])"``['NUMBER', 'PLUS', 'NUMBER', 'EOF']`
- `python -c "from calc.lexer import tokenize; tokenize('1 @ 2')"` → raises `LexError: unexpected character '@' at position 2`
**EXPECTED:** whitespace omitted from output; LexError raised with `@` and `2` in message
**WHERE:** `calc/lexer.py`
### D4 — tests green
**WHAT:** `calc/test_lexer.py` passes `python -m unittest -q` with 0 failures (14 tests).
**HOW:** `python -m unittest -q` from repo root
**EXPECTED:** `Ran 14 tests in 0.000s\nOK`
**WHERE:** `calc/test_lexer.py`
## Verification commands (exact, from repo root)
```bash
python -m unittest -q
python -c "from calc.lexer import tokenize; print([(t.kind,t.value) for t in tokenize('3.5*(1-2)')])"
python -c "from calc.lexer import tokenize; tokenize('1 @ 2')"
```
Expected outputs:
1. `Ran 14 tests in 0.000s` / `OK`
2. `[('NUMBER', 3.5), ('STAR', '*'), ('LPAREN', '('), ('NUMBER', 1), ('MINUS', '-'), ('NUMBER', 2), ('RPAREN', ')'), ('EOF', None)]`
3. `LexError: unexpected character '@' at position 2`

View File

@ -0,0 +1,78 @@
# STATUS — Phase `parse`
## DONE
Gate: D1,D2,D3,D4,D5,D6 — all PASS (Adversary-verified @2026-06-15T04:03Z, commit e9a5152)
## What is claimed
All DoD gates D1D6 implemented and all 20 parser tests pass (34 total across lex+parse).
**Commit:** see `claim(D1D6)` commit on main.
## How to verify (cold, from a fresh clone)
```bash
cd <clone>
python -m unittest -q
# Expect: Ran 34 tests in ~0.001s — OK
# D1 — precedence (*/ tighter than +-)
python -c "from calc.lexer import tokenize; from calc.parser import parse; print(repr(parse(tokenize('1+2*3'))))"
# Expected: BinOp('+', Num(1), BinOp('*', Num(2), Num(3)))
# D2 — left associativity
python -c "from calc.lexer import tokenize; from calc.parser import parse; print(repr(parse(tokenize('8-3-2'))))"
# Expected: BinOp('-', BinOp('-', Num(8), Num(3)), Num(2))
python -c "from calc.lexer import tokenize; from calc.parser import parse; print(repr(parse(tokenize('8/4/2'))))"
# Expected: BinOp('/', BinOp('/', Num(8), Num(4)), Num(2))
# D3 — parentheses override
python -c "from calc.lexer import tokenize; from calc.parser import parse; print(repr(parse(tokenize('(1+2)*3'))))"
# Expected: BinOp('*', BinOp('+', Num(1), Num(2)), Num(3))
# D4 — unary minus
python -c "from calc.lexer import tokenize; from calc.parser import parse; print(repr(parse(tokenize('-5'))))"
# Expected: Unary('-', Num(5))
python -c "from calc.lexer import tokenize; from calc.parser import parse; print(repr(parse(tokenize('-(1+2)'))))"
# Expected: Unary('-', BinOp('+', Num(1), Num(2)))
python -c "from calc.lexer import tokenize; from calc.parser import parse; print(repr(parse(tokenize('3 * -2'))))"
# Expected: BinOp('*', Num(3), Unary('-', Num(2)))
# D5 — errors raise ParseError
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: {src!r} did not raise')
except ParseError as e:
print(f'OK ParseError: {src!r}')
"
```
## Expected outputs
- `python -m unittest -q``Ran 34 tests in ...s\nOK`
- `1+2*3``BinOp('+', Num(1), BinOp('*', Num(2), Num(3)))`
- `8-3-2``BinOp('-', BinOp('-', Num(8), Num(3)), Num(2))`
- `8/4/2``BinOp('/', BinOp('/', Num(8), Num(4)), Num(2))`
- `(1+2)*3``BinOp('*', BinOp('+', Num(1), Num(2)), Num(3))`
- `-5``Unary('-', Num(5))`
- `-(1+2)``Unary('-', BinOp('+', Num(1), Num(2)))`
- `3 * -2``BinOp('*', Num(3), Unary('-', Num(2)))`
- All 5 malformed inputs → `OK ParseError`
## AST shape (for evaluator)
```
Num(value) — numeric literal; value is int or float
BinOp(op, left, right) — binary; op in {'+', '-', '*', '/'}
Unary(op, operand) — unary minus; op == '-'
```
All three are `@dataclass`, so `==` comparison works for tests.
## Where
- `calc/parser.py` — parser implementation
- `calc/test_parser.py` — 20 parser tests covering D1D5