artifacts: add calculators/ — the 30 built calculators (5/variant) + machine-docs + git logs
This commit is contained in:
@ -0,0 +1,11 @@
|
||||
# JOURNAL — phase eval
|
||||
|
||||
## Implementation notes
|
||||
|
||||
**evaluator.py**: Walks the AST recursively. Num returns its value directly. BinOp evaluates left/right then applies op. Division by zero is intercepted and re-raised as EvalError. All results pass through `_coerce()` which converts whole-valued floats to int.
|
||||
|
||||
**_coerce rule**: `if isinstance(value, float) and value == int(value): return int(value)`. This handles `4/2 = 2.0 → 2` and `-4/2 = -2.0 → -2` correctly. Pure int arithmetic stays int throughout (int + int = int in Python, so no coercion needed there).
|
||||
|
||||
**calc.py**: Catches LexError, ParseError, EvalError and prints to stderr with exit 1. No traceback exposed.
|
||||
|
||||
**test_evaluator.py**: 18 tests. D1 covers all 5 mandated expressions. D2 covers true division, EvalError raise, and confirms ZeroDivisionError doesn't escape. D3 checks isinstance for int/float. D4 uses subprocess to exercise CLI end-to-end.
|
||||
@ -0,0 +1,13 @@
|
||||
# Journal — phase `lex`
|
||||
|
||||
## 2026-06-15
|
||||
|
||||
Built `calc/lexer.py` with `Token` dataclass, `LexError`, and `tokenize()`.
|
||||
|
||||
Design notes:
|
||||
- `Token` is a dataclass with `kind: str` and `value: Union[int, float, str, None]`; EOF has `value=None`, operators carry their char as value, numbers carry the parsed numeric value.
|
||||
- Number parsing: scans while digit or `.`; uses `int()` if no dot else `float()`.
|
||||
- LexError message includes the offending character (quoted) and its 0-based position.
|
||||
- 18 tests cover all D1–D3 requirements including the plan's required expressions.
|
||||
|
||||
Committed ba1f056, then c465844 (STATUS sha update). Waiting for Adversary review.
|
||||
@ -0,0 +1,21 @@
|
||||
# JOURNAL — phase parse
|
||||
|
||||
## Build notes
|
||||
|
||||
Implemented a classic recursive-descent parser.
|
||||
|
||||
Grammar (precedence lowest → highest):
|
||||
```
|
||||
expr → term (('+' | '-') term)*
|
||||
term → unary (('*' | '/') unary)*
|
||||
unary → '-' unary | primary
|
||||
primary→ NUMBER | '(' expr ')'
|
||||
```
|
||||
|
||||
The `while` loops in `_expr` and `_term` produce left-associative trees naturally: each iteration wraps the accumulated left subtree as the new `node`, so 8-3-2 folds as ((8-3)-2).
|
||||
|
||||
Unary minus recurses right (`_unary` calls itself) to allow `--5` to parse as `Unary('-', Unary('-', Num(5)))`.
|
||||
|
||||
Empty input is caught by checking for EOF before entering `_expr`. Trailing tokens (e.g. `1 2`) are caught by the EOF check after `_expr` returns. Unclosed parens are caught in `_primary` when expecting RPAREN but finding EOF.
|
||||
|
||||
36 tests: 3 precision, 3 left-assoc, 3 paren, 4 unary, 5 error, plus 18 from lexer phase.
|
||||
@ -0,0 +1,64 @@
|
||||
# REVIEW — phase eval
|
||||
|
||||
_Adversary verifies each gate cold from own clone at commit `fe7e562`._
|
||||
|
||||
## review(init): Adversary online @2026-06-15T02:35Z
|
||||
|
||||
Eval plan read. DoD gates D1–D5 noted. Waiting for Builder to push evaluator.py, calc.py, and test_evaluator.py.
|
||||
|
||||
---
|
||||
|
||||
## D1: PASS @2026-06-15T02:37Z
|
||||
|
||||
Cold run of all five mandated expressions:
|
||||
- `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` ✓
|
||||
|
||||
Adversarial: `--5` → `5` (double-unary handled correctly via recursive `_unary`) ✓; `2*(3+4)-1` → `13` ✓
|
||||
|
||||
## D2: PASS @2026-06-15T02:37Z
|
||||
|
||||
Cold run:
|
||||
- `python calc.py "7/2"` → `3.5` ✓ (true division, not floor)
|
||||
- `python calc.py "1/0"` → `error: division by zero` to stderr, exit 1 ✓
|
||||
|
||||
Adversarial: confirmed `EvalError` is raised (not bare `ZeroDivisionError`) by catching both exception types directly in Python — `EvalError` caught, no `ZeroDivisionError` escaped ✓
|
||||
|
||||
## D3: PASS @2026-06-15T02:37Z
|
||||
|
||||
Cold run:
|
||||
- `python calc.py "4/2"` → `2` (int, no trailing `.0`) ✓
|
||||
- `python calc.py "7/2"` → `3.5` (float) ✓
|
||||
|
||||
Adversarial edge cases:
|
||||
- `-4/2` → `-2` (int, not `-2.0`) ✓
|
||||
- `0/5` → `0` (int) ✓
|
||||
- `1.5+0.5` → `2` (int, float sum coerced when whole) ✓
|
||||
- `_coerce` correctly uses `value == int(value)` check ✓
|
||||
|
||||
## D4: PASS @2026-06-15T02:37Z
|
||||
|
||||
Cold run:
|
||||
- `python calc.py "2+3*4"` → stdout `14`, exit 0 ✓
|
||||
- `python calc.py "1 +"` → stderr `error: unexpected token 'EOF' (None)`, exit 1 ✓
|
||||
- `python calc.py "1/0"` → stderr `error: division by zero`, exit 1 ✓
|
||||
- No-args case → stderr `usage: calc.py <expression>`, exit 1 ✓
|
||||
|
||||
Adversarial: confirmed zero tracebacks on stderr for both error cases (grep -c "Traceback" = 0) ✓
|
||||
|
||||
## D5: PASS @2026-06-15T02:37Z
|
||||
|
||||
```
|
||||
Ran 54 tests in 0.209s
|
||||
OK
|
||||
```
|
||||
|
||||
54 tests (18 eval + 36 lex+parse), 0 failures. No regression in prior suite.
|
||||
`calc/test_evaluator.py` covers D1 (5 tests), D2 (3 tests), D3 (4 tests), D4 as CLI (6 tests) ✓
|
||||
|
||||
---
|
||||
|
||||
Adversary verdict: all gates D1–D5 independently verified cold at `fe7e562`. Implementation is correct.
|
||||
@ -0,0 +1,43 @@
|
||||
# REVIEW-lex — Adversary verdicts
|
||||
|
||||
_Adversary verifies each gate cold from its own clone._
|
||||
|
||||
## Verdicts
|
||||
|
||||
Builder commit verified: `ba1f056`
|
||||
|
||||
### review(D1): PASS @2026-06-15T02:16Z
|
||||
|
||||
`tokenize("42")` → `[Token('NUMBER', 42), Token('EOF', None)]` ✓
|
||||
`tokenize("3.14")` → `[Token('NUMBER', 3.14), Token('EOF', None)]` ✓
|
||||
`tokenize(".5")` → `[Token('NUMBER', 0.5), Token('EOF', None)]` ✓
|
||||
`tokenize("10.")` → `[Token('NUMBER', 10.0), Token('EOF', None)]` ✓
|
||||
Integers produce `int`, floats produce `float`. Token dataclass has `kind` and `value`.
|
||||
|
||||
### review(D2): PASS @2026-06-15T02:16Z
|
||||
|
||||
`tokenize("1+2*3")` → `NUMBER PLUS NUMBER STAR NUMBER EOF` ✓
|
||||
All six kinds (PLUS, MINUS, STAR, SLASH, LPAREN, RPAREN) confirmed via test suite and manual probe.
|
||||
|
||||
### review(D3): PASS @2026-06-15T02:16Z
|
||||
|
||||
`tokenize(" 12 + 3 ")` → `[NUMBER, PLUS, NUMBER, EOF]` (spaces skipped) ✓
|
||||
`tokenize("1 @ 2")` → raises `calc.lexer.LexError: unexpected character '@' at position 2` ✓
|
||||
`tokenize("1 a 2")` → raises `LexError` ✓
|
||||
`tokenize("1 $ 2")` → raises `LexError` ✓
|
||||
Error message includes offending character and its position.
|
||||
|
||||
### review(D4): PASS @2026-06-15T02:16Z
|
||||
|
||||
```
|
||||
python -m unittest -q
|
||||
----------------------------------------------------------------------
|
||||
Ran 18 tests in 0.000s
|
||||
|
||||
OK
|
||||
```
|
||||
Test file contains all three plan-required cases: `" 12 + 3 "`, `"3.5*(1-2)"`, `"1 @ 2"` raises `LexError`. ✓
|
||||
|
||||
## Summary
|
||||
|
||||
All four gates PASS. Builder commit `ba1f056` is clean. No veto.
|
||||
@ -0,0 +1,53 @@
|
||||
# REVIEW — phase parse
|
||||
|
||||
## D1: PASS @2026-06-15T02:27:10Z
|
||||
|
||||
Cold run: `parse(tokenize('1+2*3'))` → `BinOp('+', Num(1), BinOp('*', Num(2), Num(3)))`
|
||||
Structure assertion passed. `*`/`/` correctly bind tighter than `+`/`-`.
|
||||
Also verified: `6/2+1` → `BinOp('+', BinOp('/', Num(6), Num(2)), Num(1))` (div-then-add order correct).
|
||||
Multi-level: `1+2*3+4` → `BinOp('+', BinOp('+', Num(1), BinOp('*', Num(2), Num(3))), Num(4))` ✓
|
||||
|
||||
## D2: PASS @2026-06-15T02:27:10Z
|
||||
|
||||
Cold run:
|
||||
- `8-3-2` → `BinOp('-', BinOp('-', Num(8), Num(3)), Num(2))` ✓
|
||||
- `8/4/2` → `BinOp('/', BinOp('/', Num(8), Num(4)), Num(2))` ✓
|
||||
|
||||
Left associativity correct for both addition and division via the while-loop in `_expr`/`_term`.
|
||||
|
||||
## D3: PASS @2026-06-15T02:27:10Z
|
||||
|
||||
Cold run: `(1+2)*3` → `BinOp('*', BinOp('+', Num(1), Num(2)), Num(3))` ✓
|
||||
Also verified: `((5))` → `Num(5)`, `2*(3+4)` → `BinOp('*', Num(2), BinOp('+', Num(3), Num(4)))` ✓
|
||||
|
||||
## D4: PASS @2026-06-15T02:27:10Z
|
||||
|
||||
Cold run:
|
||||
- `-5` → `Unary('-', Num(5))` ✓
|
||||
- `-(1+2)` → `Unary('-', BinOp('+', Num(1), Num(2)))` ✓
|
||||
- `3 * -2` → `BinOp('*', Num(3), Unary('-', Num(2)))` ✓
|
||||
- `--5` → `Unary('-', Unary('-', Num(5)))` ✓ (recursive _unary handles chaining)
|
||||
|
||||
## D5: PASS @2026-06-15T02:27:10Z
|
||||
|
||||
All five mandated error inputs raise `ParseError` (not any other exception):
|
||||
- `'1 +'` → ParseError: unexpected token 'EOF' ✓
|
||||
- `'(1'` → ParseError: expected ')', got 'EOF' ✓
|
||||
- `'1 2'` → ParseError: unexpected token 'NUMBER' ✓
|
||||
- `')('` → ParseError: unexpected token 'RPAREN' ✓
|
||||
- `''` → ParseError: empty input ✓
|
||||
|
||||
Adversarial: `+5` (unary plus) correctly raises ParseError (not in grammar, no crash) ✓
|
||||
|
||||
## D6: PASS @2026-06-15T02:27:10Z
|
||||
|
||||
```
|
||||
Ran 36 tests in 0.001s
|
||||
OK
|
||||
```
|
||||
|
||||
36 tests, 0 failures. Suite covers D1–D5 with structure-level assertions (not evaluation).
|
||||
|
||||
---
|
||||
|
||||
Adversary verdict: all gates D1–D6 independently verified cold. Implementation is correct.
|
||||
@ -0,0 +1,52 @@
|
||||
# STATUS — phase eval
|
||||
|
||||
## DONE
|
||||
|
||||
## Gates claimed: D1, D2, D3, D4, D5
|
||||
|
||||
### What is claimed
|
||||
|
||||
| Gate | Description |
|
||||
|------|-------------|
|
||||
| D1 | Arithmetic correctness: `+`, `-`, `*`, `/`, precedence, parens, unary minus |
|
||||
| D2 | True division; division by zero raises `EvalError` (not `ZeroDivisionError`) |
|
||||
| D3 | Whole results → `int`, non-whole → `float` |
|
||||
| D4 | CLI `calc.py`: prints result + exit 0 on success; error to stderr + non-zero on failure |
|
||||
| D5 | `calc/test_evaluator.py` (18 tests) + prior suite (36 tests) = 54 tests, 0 failures |
|
||||
|
||||
### How to verify (exact commands)
|
||||
|
||||
```bash
|
||||
cd /tmp/ao-campaign-Ofyz4E/builder-adversary-min/r3/work
|
||||
|
||||
# D5 — full suite
|
||||
python -m unittest -q
|
||||
|
||||
# D1 — arithmetic
|
||||
python calc.py "2+3*4" # expected: 14
|
||||
python calc.py "(2+3)*4" # expected: 20
|
||||
python calc.py "8-3-2" # expected: 3
|
||||
python calc.py "-2+5" # expected: 3
|
||||
python calc.py "2*-3" # expected: -6
|
||||
|
||||
# D2 — true division + EvalError
|
||||
python calc.py "7/2" # expected: 3.5
|
||||
python calc.py "1/0" # expected: error to stderr, exit non-zero
|
||||
|
||||
# D3 — result type
|
||||
python calc.py "4/2" # expected: 2 (int, no .0)
|
||||
python calc.py "7/2" # expected: 3.5 (float)
|
||||
|
||||
# D4 — CLI error handling
|
||||
python calc.py "1 +" # expected: error to stderr, exit non-zero, no traceback
|
||||
```
|
||||
|
||||
### Files added
|
||||
|
||||
- `calc/evaluator.py` — `evaluate(node) -> int | float`, `EvalError`
|
||||
- `calc.py` — top-level CLI
|
||||
- `calc/test_evaluator.py` — 18 unittest tests covering D1–D4
|
||||
|
||||
### Commit SHA
|
||||
|
||||
`fe7e562080ee15b1f13f962171cdc4719734b062`
|
||||
@ -0,0 +1,56 @@
|
||||
# Status — phase `lex`
|
||||
|
||||
## Claimed gates
|
||||
|
||||
- **D1** — integers and floats tokenize to NUMBER with correct numeric value
|
||||
- **D2** — `+ - * / ( )` tokenize to the right kinds
|
||||
- **D3** — whitespace skipped; invalid chars raise `LexError` with offending char and position
|
||||
- **D4** — `calc/test_lexer.py` passes `python -m unittest` with 0 failures
|
||||
|
||||
## How to verify (exact commands)
|
||||
|
||||
```bash
|
||||
cd <repo-root>
|
||||
|
||||
# D4 — all tests green
|
||||
python -m unittest -q
|
||||
|
||||
# D1/D2 — token kinds and values for 3.5*(1-2)
|
||||
python -c "from calc.lexer import tokenize; print([(t.kind,t.value) for t in tokenize('3.5*(1-2)')])"
|
||||
|
||||
# D3 — LexError raised for invalid char
|
||||
python -c "from calc.lexer import tokenize; tokenize('1 @ 2')"
|
||||
```
|
||||
|
||||
## Expected results
|
||||
|
||||
`python -m unittest -q`:
|
||||
```
|
||||
Ran 18 tests in 0.000s
|
||||
|
||||
OK
|
||||
```
|
||||
|
||||
`tokenize('3.5*(1-2)')`:
|
||||
```
|
||||
[('NUMBER', 3.5), ('STAR', '*'), ('LPAREN', '('), ('NUMBER', 1), ('MINUS', '-'), ('NUMBER', 2), ('RPAREN', ')'), ('EOF', None)]
|
||||
```
|
||||
|
||||
`tokenize('1 @ 2')`:
|
||||
```
|
||||
raises calc.lexer.LexError: unexpected character '@' at position 2
|
||||
```
|
||||
|
||||
## Files
|
||||
|
||||
- `calc/__init__.py` — package marker
|
||||
- `calc/lexer.py` — `Token`, `LexError`, `tokenize()`
|
||||
- `calc/test_lexer.py` — 18 unittest cases covering D1–D3
|
||||
|
||||
## Commit SHA
|
||||
|
||||
`ba1f056` — pushed to `origin/main`
|
||||
|
||||
## DONE
|
||||
|
||||
All gates D1–D4 received PASS verdicts from the Adversary (REVIEW-lex.md @2026-06-15T02:16Z). No veto. Phase `lex` is complete.
|
||||
@ -0,0 +1,91 @@
|
||||
# STATUS — phase parse
|
||||
|
||||
## DONE
|
||||
|
||||
## Claimed gates: D1, D2, D3, D4, D5, D6
|
||||
|
||||
Commit: fa50146a5acbc9e7cf65a1e60e0b0fba2d8bd2ea (main)
|
||||
Files: `calc/parser.py`, `calc/test_parser.py`
|
||||
|
||||
---
|
||||
|
||||
## What is claimed
|
||||
|
||||
- **D1 — precedence**: `*`/`/` bind tighter than `+`/`-`
|
||||
- **D2 — left associativity**: same-precedence ops are left-associative
|
||||
- **D3 — parentheses**: parens override default precedence
|
||||
- **D4 — unary minus**: leading and nested unary minus handled
|
||||
- **D5 — errors**: malformed inputs raise `ParseError` (not any other exception)
|
||||
- **D6 — tests green**: `python -m unittest -q` → 36 tests, 0 failures
|
||||
|
||||
---
|
||||
|
||||
## How to verify (exact commands)
|
||||
|
||||
```bash
|
||||
# D6 — all tests green
|
||||
python -m unittest -q
|
||||
|
||||
# D1 — mul binds tighter than add
|
||||
python -c "from calc.lexer import tokenize; from calc.parser import parse; t=parse(tokenize('1+2*3')); print(t); assert str(t)==\"BinOp('+', Num(1), BinOp('*', Num(2), Num(3)))\""
|
||||
|
||||
# D2 — left associativity subtraction
|
||||
python -c "from calc.lexer import tokenize; from calc.parser import parse; t=parse(tokenize('8-3-2')); print(t); assert str(t)==\"BinOp('-', BinOp('-', Num(8), Num(3)), Num(2))\""
|
||||
|
||||
# D2 — left associativity division
|
||||
python -c "from calc.lexer import tokenize; from calc.parser import parse; t=parse(tokenize('8/4/2')); print(t); assert str(t)==\"BinOp('/', BinOp('/', Num(8), Num(4)), Num(2))\""
|
||||
|
||||
# D3 — parens override precedence
|
||||
python -c "from calc.lexer import tokenize; from calc.parser import parse; t=parse(tokenize('(1+2)*3')); print(t); assert str(t)==\"BinOp('*', BinOp('+', Num(1), Num(2)), Num(3))\""
|
||||
|
||||
# D4 — unary minus
|
||||
python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('-5'))); print(parse(tokenize('-(1+2)'))); print(parse(tokenize('3 * -2')))"
|
||||
|
||||
# D5 — all five error inputs 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('FAIL — no error for', repr(src))
|
||||
except ParseError:
|
||||
print('OK', repr(src))
|
||||
"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Expected output
|
||||
|
||||
**D6**: `Ran 36 tests ... OK`
|
||||
|
||||
**D1**: `BinOp('+', Num(1), BinOp('*', Num(2), Num(3)))` (no assertion error)
|
||||
|
||||
**D2-sub**: `BinOp('-', BinOp('-', Num(8), Num(3)), Num(2))` (no assertion error)
|
||||
|
||||
**D2-div**: `BinOp('/', BinOp('/', Num(8), Num(4)), Num(2))` (no assertion error)
|
||||
|
||||
**D3**: `BinOp('*', BinOp('+', Num(1), Num(2)), Num(3))` (no assertion error)
|
||||
|
||||
**D4**:
|
||||
```
|
||||
Unary('-', Num(5))
|
||||
Unary('-', BinOp('+', Num(1), Num(2)))
|
||||
BinOp('*', Num(3), Unary('-', Num(2)))
|
||||
```
|
||||
|
||||
**D5**: Five lines all starting with `OK`
|
||||
|
||||
---
|
||||
|
||||
## AST shape reference
|
||||
|
||||
```
|
||||
Num(value) # numeric literal; value: int | float
|
||||
BinOp(op, left, right) # binary op; op in {'+', '-', '*', '/'}
|
||||
Unary(op, operand) # unary op; op == '-'
|
||||
```
|
||||
|
||||
Precedence (high → low): unary-minus > `*` `/` > `+` `-`
|
||||
Associativity: left for all binary operators
|
||||
Reference in New Issue
Block a user