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 @@
calc/__pycache__/

View File

@ -0,0 +1,18 @@
# git history (claim/review handshake), from the run's shared bare repo
71359e7 status(eval): ## DONE — all D1-D5 Adversary-verified PASS
d51b359 review(D1-D5): PASS — all eval gates verified; arithmetic, division, types, CLI, tests all correct
3b507ee claim(D1-D5): implement evaluator, CLI, and tests — all eval gates
a093fc4 review(eval): Adversary initialized — watching for gate claims
3ede551 status(parse): ## DONE — all D1-D6 Adversary-verified PASS
1b959eb review(D1-D6): PASS — all parse gates verified; precedence, assoc, parens, unary, errors, tests all correct
d20b751 claim(D1-D6): implement parser — all gates claimed
2a9645d review(parse): Adversary initialized for parse phase — watching for gate claims
10ff032 status(lex): ## DONE — all D1-D4 Adversary-verified PASS
25d205d review(D3): PASS — AF-1 fixed, bare-dot now raises LexError; all D1-D4 PASS, VETO lifted
10a2f15 status(lex): record fix commit sha f40a364
f40a364 claim(D3): fix AF-1 bare-dot ValueError leak, add test
c46f537 review(D3): FAIL — bare '.' leaks ValueError not LexError (AF-1); D1 D2 D4 PASS; VETO until fixed
f72e487 claim(D1,D2,D3,D4): all lex gates claimed, tests green
fb03159 feat(lex): implement lexer D1-D3 and test suite D4
cabf40a review(init): Adversary initialized, watching for gate claims
20b6566 chore: seed

View File

@ -0,0 +1 @@
# calc work repo

View File

@ -0,0 +1 @@
original path: /tmp/ao-campaign-ufRkmF/builder-adversary-stateless/r5

View File

@ -0,0 +1,27 @@
"""CLI entry point: python calc.py "<expression>" """
import sys
from calc.lexer import tokenize, LexError
from calc.parser import parse, ParseError
from calc.evaluator import evaluate, EvalError
def main():
if len(sys.argv) != 2:
print("usage: calc.py <expression>", file=sys.stderr)
sys.exit(1)
expr = sys.argv[1]
try:
tokens = tokenize(expr)
ast = parse(tokens)
result = evaluate(ast)
print(result)
except (LexError, ParseError, EvalError) as e:
print(f"error: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,42 @@
"""Evaluator for arithmetic AST nodes produced by calc.parser."""
from calc.parser import Num, BinOp, Unary
class EvalError(Exception):
pass
def evaluate(node) -> int | float:
"""Walk the AST and return the numeric result.
Whole-valued floats are returned as int (e.g. 4/2 → 2, not 2.0).
Division by zero raises EvalError.
"""
if isinstance(node, Num):
return node.value
if isinstance(node, Unary):
operand = evaluate(node.operand)
if node.op == "-":
return -operand
raise EvalError(f"unknown unary operator: {node.op!r}")
if isinstance(node, BinOp):
left = evaluate(node.left)
right = evaluate(node.right)
if node.op == "+":
result = left + right
elif node.op == "-":
result = left - right
elif node.op == "*":
result = left * right
elif node.op == "/":
if right == 0:
raise EvalError("division by zero")
result = left / right
else:
raise EvalError(f"unknown operator: {node.op!r}")
# Normalize: whole-valued float → int
if isinstance(result, float) and result == int(result):
return int(result)
return result
raise EvalError(f"unknown node type: {type(node).__name__}")

View File

@ -0,0 +1,60 @@
"""Lexer for arithmetic calculator."""
class LexError(Exception):
pass
class Token:
__slots__ = ("kind", "value")
def __init__(self, kind: str, value):
self.kind = kind
self.value = value
def __repr__(self):
return f"Token({self.kind}, {self.value!r})"
def __eq__(self, other):
return isinstance(other, Token) and self.kind == other.kind and self.value == other.value
_SINGLE = {
"+": "PLUS",
"-": "MINUS",
"*": "STAR",
"/": "SLASH",
"(": "LPAREN",
")": "RPAREN",
}
def tokenize(src: str) -> list:
tokens = []
i = 0
n = len(src)
while i < n:
ch = src[i]
if ch in " \t":
i += 1
elif ch in _SINGLE:
tokens.append(Token(_SINGLE[ch], ch))
i += 1
elif ch.isdigit() or ch == ".":
j = i
has_dot = False
while j < n and (src[j].isdigit() or (src[j] == "." and not has_dot)):
if src[j] == ".":
has_dot = True
j += 1
raw = src[i:j]
try:
value = float(raw) if has_dot else int(raw)
except ValueError:
raise LexError(f"invalid number literal {raw!r} at position {i}")
tokens.append(Token("NUMBER", value))
i = j
else:
raise LexError(f"unexpected character {ch!r} at position {i}")
tokens.append(Token("EOF", None))
return tokens

View File

@ -0,0 +1,144 @@
"""Recursive-descent parser for arithmetic expressions.
AST nodes:
Num(value) -- numeric literal (int or float)
BinOp(op, left, right) -- binary op; op in {'+', '-', '*', '/'}
Unary(op, operand) -- unary op; op == '-'
Grammar:
expr → term (('+' | '-') term)*
term → unary (('*' | '/') unary)*
unary → '-' unary | primary
primary → NUMBER | '(' expr ')'
"""
class ParseError(Exception):
pass
class Num:
__slots__ = ("value",)
def __init__(self, value):
self.value = value
def __repr__(self):
return f"Num({self.value!r})"
def __eq__(self, other):
return isinstance(other, Num) and self.value == other.value
class BinOp:
__slots__ = ("op", "left", "right")
def __init__(self, op, left, right):
self.op = op
self.left = left
self.right = right
def __repr__(self):
return f"BinOp({self.op!r}, {self.left!r}, {self.right!r})"
def __eq__(self, other):
return (
isinstance(other, BinOp)
and self.op == other.op
and self.left == other.left
and self.right == other.right
)
class Unary:
__slots__ = ("op", "operand")
def __init__(self, op, operand):
self.op = op
self.operand = operand
def __repr__(self):
return f"Unary({self.op!r}, {self.operand!r})"
def __eq__(self, other):
return (
isinstance(other, Unary)
and self.op == other.op
and self.operand == other.operand
)
class _Parser:
def __init__(self, tokens):
self._tokens = tokens
self._pos = 0
def _peek(self):
return self._tokens[self._pos]
def _consume(self, kind=None):
tok = self._tokens[self._pos]
if kind is not None and tok.kind != kind:
raise ParseError(
f"expected {kind}, got {tok.kind!r} ({tok.value!r})"
)
self._pos += 1
return tok
def parse(self):
if self._peek().kind == "EOF":
raise ParseError("empty input")
node = self._expr()
if self._peek().kind != "EOF":
tok = self._peek()
raise ParseError(
f"unexpected token {tok.kind!r} ({tok.value!r}) after expression"
)
return node
def _expr(self):
node = self._term()
while self._peek().kind in ("PLUS", "MINUS"):
op = self._consume().value
right = self._term()
node = BinOp(op, node, right)
return node
def _term(self):
node = self._unary()
while self._peek().kind in ("STAR", "SLASH"):
op = self._consume().value
right = self._unary()
node = BinOp(op, node, right)
return node
def _unary(self):
if self._peek().kind == "MINUS":
op = self._consume().value
operand = self._unary()
return Unary(op, operand)
return self._primary()
def _primary(self):
tok = self._peek()
if tok.kind == "NUMBER":
self._consume()
return Num(tok.value)
if tok.kind == "LPAREN":
self._consume("LPAREN")
node = self._expr()
if self._peek().kind != "RPAREN":
raise ParseError("unclosed parenthesis")
self._consume("RPAREN")
return node
if tok.kind == "EOF":
raise ParseError("unexpected end of input")
raise ParseError(f"unexpected token {tok.kind!r} ({tok.value!r})")
def parse(tokens) -> object:
"""Parse a token list (from tokenize()) into an AST node.
Raises ParseError on malformed input.
"""
return _Parser(tokens).parse()

View File

@ -0,0 +1,92 @@
"""Tests for calc.evaluator — covers D1 (arithmetic), D2 (division/EvalError), D3 (result type)."""
import unittest
from calc.lexer import tokenize
from calc.parser import parse
from calc.evaluator import evaluate, EvalError
def calc(s):
return evaluate(parse(tokenize(s)))
class TestArithmetic(unittest.TestCase):
"""D1 — arithmetic: +, -, *, /, precedence, parens, unary minus."""
def test_add_mul_precedence(self):
self.assertEqual(calc("2+3*4"), 14)
def test_parens(self):
self.assertEqual(calc("(2+3)*4"), 20)
def test_left_assoc_subtract(self):
self.assertEqual(calc("8-3-2"), 3)
def test_unary_minus_add(self):
self.assertEqual(calc("-2+5"), 3)
def test_unary_minus_mul(self):
self.assertEqual(calc("2*-3"), -6)
def test_simple_add(self):
self.assertEqual(calc("1+2"), 3)
def test_simple_sub(self):
self.assertEqual(calc("5-3"), 2)
def test_simple_mul(self):
self.assertEqual(calc("3*4"), 12)
def test_nested_parens(self):
self.assertEqual(calc("((2+3))"), 5)
class TestDivision(unittest.TestCase):
"""D2 — true division and EvalError on divide-by-zero."""
def test_true_division(self):
self.assertEqual(calc("7/2"), 3.5)
def test_divide_by_zero_raises(self):
with self.assertRaises(EvalError):
calc("1/0")
def test_divide_by_zero_expression(self):
with self.assertRaises(EvalError):
calc("5/(2-2)")
def test_no_bare_zero_division_error(self):
"""ZeroDivisionError must NOT escape the API."""
try:
calc("1/0")
except EvalError:
pass
except ZeroDivisionError:
self.fail("ZeroDivisionError escaped — must be EvalError")
class TestResultType(unittest.TestCase):
"""D3 — whole-valued results are int, non-whole are float."""
def test_whole_division_is_int(self):
result = calc("4/2")
self.assertEqual(result, 2)
self.assertIsInstance(result, int)
def test_non_whole_division_is_float(self):
result = calc("7/2")
self.assertEqual(result, 3.5)
self.assertIsInstance(result, float)
def test_integer_add_is_int(self):
result = calc("2+3")
self.assertIsInstance(result, int)
def test_integer_mul_is_int(self):
result = calc("3*4")
self.assertIsInstance(result, int)
if __name__ == "__main__":
unittest.main()

View File

@ -0,0 +1,126 @@
"""Tests for calc/lexer.py covering D1-D3."""
import unittest
from calc.lexer import tokenize, Token, LexError
def kinds(src):
return [t.kind for t in tokenize(src)]
def kv(src):
return [(t.kind, t.value) for t in tokenize(src)]
class TestNumbers(unittest.TestCase):
def test_integer(self):
result = tokenize("42")
self.assertEqual(result, [Token("NUMBER", 42), Token("EOF", None)])
self.assertIsInstance(result[0].value, int)
def test_float_standard(self):
result = tokenize("3.14")
self.assertEqual(len(result), 2)
self.assertEqual(result[0].kind, "NUMBER")
self.assertAlmostEqual(result[0].value, 3.14)
self.assertIsInstance(result[0].value, float)
def test_float_leading_dot(self):
result = tokenize(".5")
self.assertEqual(result[0].kind, "NUMBER")
self.assertAlmostEqual(result[0].value, 0.5)
def test_float_trailing_dot(self):
result = tokenize("10.")
self.assertEqual(result[0].kind, "NUMBER")
self.assertAlmostEqual(result[0].value, 10.0)
self.assertIsInstance(result[0].value, float)
def test_zero(self):
result = tokenize("0")
self.assertEqual(result[0], Token("NUMBER", 0))
class TestOperatorsAndParens(unittest.TestCase):
def test_plus(self):
self.assertIn(Token("PLUS", "+"), tokenize("+"))
def test_minus(self):
self.assertIn(Token("MINUS", "-"), tokenize("-"))
def test_star(self):
self.assertIn(Token("STAR", "*"), tokenize("*"))
def test_slash(self):
self.assertIn(Token("SLASH", "/"), tokenize("/"))
def test_lparen(self):
self.assertIn(Token("LPAREN", "("), tokenize("("))
def test_rparen(self):
self.assertIn(Token("RPAREN", ")"), tokenize(")"))
def test_expression_kinds(self):
self.assertEqual(
kinds("1+2*3"),
["NUMBER", "PLUS", "NUMBER", "STAR", "NUMBER", "EOF"],
)
def test_eof_last(self):
result = tokenize("1+2")
self.assertEqual(result[-1].kind, "EOF")
class TestWhitespaceAndErrors(unittest.TestCase):
def test_spaces_skipped(self):
self.assertEqual(
kinds(" 12 + 3 "),
["NUMBER", "PLUS", "NUMBER", "EOF"],
)
result = tokenize(" 12 + 3 ")
self.assertEqual(result[0].value, 12)
self.assertEqual(result[2].value, 3)
def test_tabs_skipped(self):
self.assertEqual(kinds("1\t+\t2"), ["NUMBER", "PLUS", "NUMBER", "EOF"])
def test_complex_expression(self):
result = kv("3.5*(1-2)")
self.assertEqual(
result,
[
("NUMBER", 3.5),
("STAR", "*"),
("LPAREN", "("),
("NUMBER", 1),
("MINUS", "-"),
("NUMBER", 2),
("RPAREN", ")"),
("EOF", None),
],
)
def test_at_raises_lex_error(self):
with self.assertRaises(LexError):
tokenize("1 @ 2")
def test_dollar_raises_lex_error(self):
with self.assertRaises(LexError):
tokenize("$")
def test_letter_raises_lex_error(self):
with self.assertRaises(LexError):
tokenize("x")
def test_lex_error_message_has_char_and_pos(self):
try:
tokenize("1 @ 2")
except LexError as e:
self.assertIn("@", str(e))
def test_bare_dot_raises_lex_error(self):
with self.assertRaises(LexError):
tokenize(".")
if __name__ == "__main__":
unittest.main()

View File

@ -0,0 +1,154 @@
"""Tests for calc/parser.py — covers D1D5."""
import unittest
from calc.lexer import tokenize
from calc.parser import BinOp, Num, ParseError, Unary, parse
def p(src):
return parse(tokenize(src))
class TestPrecedence(unittest.TestCase):
"""D1 — * and / bind tighter than + and -."""
def test_add_mul(self):
# 1+2*3 → BinOp('+', Num(1), BinOp('*', Num(2), Num(3)))
self.assertEqual(p("1+2*3"), BinOp("+", Num(1), BinOp("*", Num(2), Num(3))))
def test_mul_add(self):
# 2*3+4 → BinOp('+', BinOp('*', Num(2), Num(3)), Num(4))
self.assertEqual(p("2*3+4"), BinOp("+", BinOp("*", Num(2), Num(3)), Num(4)))
def test_add_div(self):
# 1+8/4 → BinOp('+', Num(1), BinOp('/', Num(8), Num(4)))
self.assertEqual(p("1+8/4"), BinOp("+", Num(1), BinOp("/", Num(8), Num(4))))
def test_sub_mul(self):
# 10-2*3 → BinOp('-', Num(10), BinOp('*', Num(2), Num(3)))
self.assertEqual(
p("10-2*3"), BinOp("-", Num(10), BinOp("*", Num(2), Num(3)))
)
def test_single_number(self):
self.assertEqual(p("42"), Num(42))
def test_single_float(self):
self.assertEqual(p("3.14"), Num(3.14))
class TestLeftAssociativity(unittest.TestCase):
"""D2 — same-precedence operators associate left."""
def test_sub_sub(self):
# 8-3-2 → BinOp('-', BinOp('-', Num(8), Num(3)), Num(2))
self.assertEqual(
p("8-3-2"), BinOp("-", BinOp("-", Num(8), Num(3)), Num(2))
)
def test_div_div(self):
# 8/4/2 → BinOp('/', BinOp('/', Num(8), Num(4)), Num(2))
self.assertEqual(
p("8/4/2"), BinOp("/", BinOp("/", Num(8), Num(4)), Num(2))
)
def test_add_add(self):
# 1+2+3 → BinOp('+', BinOp('+', Num(1), Num(2)), Num(3))
self.assertEqual(
p("1+2+3"), BinOp("+", BinOp("+", Num(1), Num(2)), Num(3))
)
def test_mul_mul(self):
# 2*3*4 → BinOp('*', BinOp('*', Num(2), Num(3)), Num(4))
self.assertEqual(
p("2*3*4"), BinOp("*", BinOp("*", Num(2), Num(3)), Num(4))
)
class TestParentheses(unittest.TestCase):
"""D3 — parentheses override precedence."""
def test_parens_override_mul(self):
# (1+2)*3 → BinOp('*', BinOp('+', Num(1), Num(2)), Num(3))
self.assertEqual(
p("(1+2)*3"), BinOp("*", BinOp("+", Num(1), Num(2)), Num(3))
)
def test_nested_parens(self):
# ((1+2)) → BinOp('+', Num(1), Num(2))
self.assertEqual(p("((1+2))"), BinOp("+", Num(1), Num(2)))
def test_parens_inside_add(self):
# 1+(2+3) → BinOp('+', Num(1), BinOp('+', Num(2), Num(3)))
self.assertEqual(
p("1+(2+3)"), BinOp("+", Num(1), BinOp("+", Num(2), Num(3)))
)
def test_parens_alone(self):
self.assertEqual(p("(5)"), Num(5))
class TestUnaryMinus(unittest.TestCase):
"""D4 — unary minus."""
def test_leading_unary(self):
# -5 → Unary('-', Num(5))
self.assertEqual(p("-5"), Unary("-", Num(5)))
def test_unary_of_paren(self):
# -(1+2) → Unary('-', BinOp('+', Num(1), Num(2)))
self.assertEqual(
p("-(1+2)"), Unary("-", BinOp("+", Num(1), Num(2)))
)
def test_mul_unary(self):
# 3 * -2 → BinOp('*', Num(3), Unary('-', Num(2)))
self.assertEqual(
p("3 * -2"), BinOp("*", Num(3), Unary("-", Num(2)))
)
def test_double_unary(self):
# --5 → Unary('-', Unary('-', Num(5)))
self.assertEqual(p("--5"), Unary("-", Unary("-", Num(5))))
def test_unary_in_add(self):
# 1 + -2 → BinOp('+', Num(1), Unary('-', Num(2)))
self.assertEqual(
p("1 + -2"), BinOp("+", Num(1), Unary("-", Num(2)))
)
class TestErrors(unittest.TestCase):
"""D5 — malformed input raises ParseError."""
def _raises(self, src):
with self.assertRaises(ParseError):
p(src)
def test_trailing_op(self):
self._raises("1 +")
def test_unclosed_paren(self):
self._raises("(1")
def test_two_numbers(self):
self._raises("1 2")
def test_close_before_open(self):
self._raises(")(")
def test_empty(self):
self._raises("")
def test_just_op(self):
self._raises("*")
def test_mismatched_parens(self):
self._raises("(1+2")
def test_extra_close(self):
self._raises("(1+2))")
if __name__ == "__main__":
unittest.main()

View File

@ -0,0 +1,9 @@
# BACKLOG-eval.md
## Build backlog
All items complete. Gates D1D5 claimed.
## Adversary findings
(none yet)

View File

@ -0,0 +1,14 @@
# BACKLOG-lex.md
## Build backlog
- [x] D1 — Numbers: integers and floats tokenize correctly
- [x] D2 — Operators & parens: all six symbols tokenize correctly
- [x] D3 — Whitespace skipped; invalid chars raise LexError
- [x] D4 — 20 unit tests passing under python -m unittest
All items complete. Awaiting Adversary PASS verdicts.
## Adversary findings
_(Adversary writes here)_

View File

@ -0,0 +1,16 @@
# BACKLOG-parse.md — Phase `parse`
## Build backlog
- [x] Write calc/parser.py (ParseError, Num, BinOp, Unary, parse())
- [x] Write calc/test_parser.py (unittest, D1-D5 coverage)
- [x] Claim D1 (precedence)
- [x] Claim D2 (left associativity)
- [x] Claim D3 (parentheses)
- [x] Claim D4 (unary minus)
- [x] Claim D5 (errors)
- [x] Claim D6 (tests green)
- [ ] Await Adversary verdict on D1D6
## Adversary findings
*(None yet — awaiting gate claims)*

View File

@ -0,0 +1,5 @@
# DECISIONS.md — Shared (append-only)
## 2026-06-15T04:30:28Z — Adversary initialized
Adversary loop started for phase `lex`. No decisions yet.

View File

@ -0,0 +1,35 @@
# JOURNAL-eval.md
## Session 1
### Implementation
Created three files:
- `calc/evaluator.py``evaluate(node)` walks Num/BinOp/Unary nodes. Division uses Python `/` (true division). After division, normalizes whole-valued floats to int via `int(result)`.
- `calc/test_evaluator.py` — 16 tests covering D1 arithmetic (all 5 DoD cases + extras), D2 division/EvalError, D3 type normalization.
- `calc.py` — CLI: parses argv[1], calls tokenize→parse→evaluate, prints result; catches LexError/ParseError/EvalError, prints to stderr, exits 1.
### Verification run
```
$ python -m unittest -q
Ran 65 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" 2>&1; echo "exit: $?"
error: division by zero
exit: 1
$ python calc.py "1 +" 2>&1; echo "exit: $?"
error: unexpected end of input
exit: 1
```
All outputs match DoD specs. 65 tests (lex + parse + eval) pass, zero failures.

View File

@ -0,0 +1,41 @@
# JOURNAL-lex.md — Adversary Journal
## 2026-06-15T04:30:28Z — Initialized
Adversary started. Phase plan read. No Builder work present yet.
Watching for gate claims in machine-docs/STATUS-lex.md.
---
# JOURNAL-lex.md — Builder Entries
## 2026-06-15 — Implementation complete
Built `calc/lexer.py` and `calc/test_lexer.py` in a single session.
**Design choices:**
- `Token` uses `__slots__` for memory efficiency; `__eq__` for test assertions.
- Number parsing: scan ahead consuming digits and at most one `.`. A leading dot (`.5`) and trailing dot (`10.`) both work because the while loop checks `src[j] == "."` only when `not has_dot`.
- Integer values stored as `int`, floats as `float` — allows parser to distinguish.
- `LexError` extends `Exception` directly; message includes the character repr and its position.
**Test run:**
```
$ python -m unittest -q
....................
----------------------------------------------------------------------
Ran 20 tests in 0.000s
OK
```
**Verification commands run locally:**
```
$ 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:
...
calc.lexer.LexError: unexpected character '@' at position 2
```

View File

@ -0,0 +1,32 @@
# JOURNAL-parse.md
## 2026-06-15 — Build + verify
Lex phase DONE. Token kinds: NUMBER, PLUS, MINUS, STAR, SLASH, LPAREN, RPAREN, EOF.
Grammar chosen (iterative at each level → left associativity by construction):
```
expr → term (('+' | '-') term)*
term → unary (('*' | '/') unary)*
unary → '-' unary | primary
primary → NUMBER | '(' expr ')'
```
### Manual verification output
```
D1 1+2*3: BinOp('+', Num(1), BinOp('*', Num(2), Num(3)))
D2 8-3-2: BinOp('-', BinOp('-', Num(8), Num(3)), Num(2))
D2 8/4/2: BinOp('/', BinOp('/', Num(8), Num(4)), Num(2))
D3 (1+2)*3: BinOp('*', BinOp('+', Num(1), Num(2)), Num(3))
D4 -5: Unary('-', Num(5))
D4 -(1+2): Unary('-', BinOp('+', Num(1), Num(2)))
D4 3*-2: BinOp('*', Num(3), Unary('-', Num(2)))
D5 '1 +' -> ParseError: unexpected end of input
D5 '(1' -> ParseError: unclosed parenthesis
D5 '1 2' -> ParseError: unexpected token 'NUMBER' (2) after expression
D5 ')(' -> ParseError: unexpected token 'RPAREN' (')')
D5 '' -> ParseError: empty input
```
Test run: `Ran 48 tests in 0.001s — OK` (21 lex + 27 parser)

View File

@ -0,0 +1,60 @@
# REVIEW-eval.md — Adversary verdicts
## eval/D1: PASS @2026-06-15T04:45:33Z
**Evidence (cold run, commit 3b507ee):**
```
2+3*4 → 14 ✓
(2+3)*4 → 20 ✓
8-3-2 → 3 ✓
-2+5 → 3 ✓
2*-3 → -6 ✓
```
Extra probes: `--5`→5, `-(3+4)`→-7, `1+2+3+4+5`→15 ✓
---
## eval/D2: PASS @2026-06-15T04:45:33Z
**Evidence:**
- `7/2``3.5` (true division, not integer) ✓
- `1/0``EvalError: division by zero` (not bare ZeroDivisionError) ✓
- `5/(2-2)``EvalError`
---
## eval/D3: PASS @2026-06-15T04:45:33Z
**Evidence:**
```
4/2 → int 2 ✓
7/2 → float 3.5 ✓
3*2 → int 6 ✓
-4/2 → int -2 ✓
1/3 → float 0.333... ✓
2.5*2 → int 5 ✓
```
---
## eval/D4: PASS @2026-06-15T04:45:33Z
**Evidence:**
```
python calc.py "2+3*4" → stdout: 14, exit 0 ✓
python calc.py "1/0" → stderr: "error: division by zero", exit 1, empty stdout ✓
python calc.py "1 +" → stderr: "error: unexpected end of input", exit 1 ✓
python calc.py → exit 1 (usage error) ✓
python calc.py "1+1" "extra" → exit 1 (too many args) ✓
```
---
## eval/D5: PASS @2026-06-15T04:45:33Z
**Evidence:**
```
Ran 65 tests in 0.001s OK (21 lex + 27 parser + 17 evaluator) ✓
```
- `test_evaluator.py` covers D1 (arithmetic), D2 (division/EvalError), D3 (result type) ✓
- No regressions in lex or parse suites ✓

View File

@ -0,0 +1,69 @@
# REVIEW-lex.md — Adversary Verdicts
## Gates
- D1: PASS @2026-06-15T04:34:35Z
- D2: PASS @2026-06-15T04:34:35Z
- D3: PASS @2026-06-15T04:40:18Z (re-verified after AF-1 fix)
- D4: PASS @2026-06-15T04:40:18Z (21 tests, 0 failures)
All DoD items verified. VETO lifted.
---
## D1 — Numbers: PASS @2026-06-15T04:34:35Z
```
NUMBER 42 int ✓
NUMBER 3.14 float ✓
NUMBER 0.5 ✓ (.5 leading dot)
NUMBER 10.0 float ✓ (10. trailing dot)
[('NUMBER', 42), ('EOF', None)] ✓
```
---
## D2 — Operators & Parens: PASS @2026-06-15T04:34:35Z
```
['NUMBER', 'PLUS', 'NUMBER', 'STAR', 'NUMBER', 'EOF'] ✓ (1+2*3)
[('PLUS', '+'), ('MINUS', '-'), ('STAR', '*'), ('SLASH', '/'), ('LPAREN', '('), ('RPAREN', ')'), ('EOF', None)] ✓
```
---
## D3 — Whitespace & Errors: PASS @2026-06-15T04:40:18Z
**First verdict (FAIL @2026-06-15T04:34:35Z):** bare `.` raised `ValueError` not `LexError` (AF-1).
**Re-verified after fix commit f40a364:**
```
Ran 21 tests in 0.000s — OK ✓
['NUMBER', 'PLUS', 'NUMBER', 'EOF'] (whitespace skipped) ✓
tokenize('.') → LexError: invalid number literal '.' at position 0 ✓
tokenize('1 @ 2') → LexError: unexpected character '@' at position 2 ✓
```
Valid floats unaffected by fix: `.5``0.5`, `10.``10.0`, `3.5*(1-2)` correct.
---
## D4 — Tests Green: PASS @2026-06-15T04:40:18Z
```
Ran 21 tests in 0.000s
OK
```
21 tests (was 20; `test_bare_dot_raises_lex_error` added). All three required DoD examples covered and passing.
---
## Adversary findings
### AF-1 (CLOSED @2026-06-15T04:40:18Z): Bare `.` leaked `ValueError` instead of `LexError`
**Fix:** `lexer.py` now wraps `float(raw)` / `int(raw)` in try/except, re-raises as `LexError`. Test added.
Re-tested: `tokenize('.')` now raises `LexError: invalid number literal '.' at position 0`. ✓
_Closed by Adversary after re-test._

View File

@ -0,0 +1,67 @@
# REVIEW-parse.md — Adversary verdicts for phase `parse`
Phase plan SSOT: /home/loops/project-orchestrator/projects/agent-orchestrator-benchmark/plans/calc/parse.md
Gates: D1 (precedence), D2 (left associativity), D3 (parentheses), D4 (unary minus), D5 (errors), D6 (tests green)
## Verdicts
- D1: PASS @2026-06-15T04:41:47Z
- D2: PASS @2026-06-15T04:41:47Z
- D3: PASS @2026-06-15T04:41:47Z
- D4: PASS @2026-06-15T04:41:47Z
- D5: PASS @2026-06-15T04:41:47Z
- D6: PASS @2026-06-15T04:41:47Z
All DoD gates PASS. No VETO. Phase `parse` complete — Builder may write ## DONE to STATUS-parse.md.
---
## Evidence
**D1 — Precedence**
```
1+2*3 → BinOp('+', Num(1), BinOp('*', Num(2), Num(3))) ✓ (* binds tighter)
2*3+4 → BinOp('+', BinOp('*', Num(2), Num(3)), Num(4)) ✓
1+2*3-4/2 → BinOp('-', BinOp('+', Num(1), BinOp('*', Num(2), Num(3))), BinOp('/', Num(4), Num(2))) ✓
```
**D2 — Left Associativity**
```
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)) ✓
2/3*4 → BinOp('*', BinOp('/', Num(2), Num(3)), Num(4)) ✓
```
**D3 — Parentheses**
```
(1+2)*3 → BinOp('*', BinOp('+', Num(1), Num(2)), Num(3)) ✓
((((1)))) → Num(1) ✓
```
**D4 — Unary Minus**
```
-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))) ✓
-2*3 → BinOp('*', Unary('-', Num(2)), Num(3)) ✓
2+-3 → BinOp('+', Num(2), Unary('-', Num(3))) ✓
-2*-3 → BinOp('*', Unary('-', Num(2)), Unary('-', Num(3))) ✓
```
**D5 — Errors (all raise ParseError, no wrong exception types)**
```
'1 +' → ParseError: unexpected end of input ✓
'(1' → ParseError: unclosed parenthesis ✓
'1 2' → ParseError: unexpected token 'NUMBER'… ✓
')(' → ParseError: unexpected token 'RPAREN' ✓
'' → ParseError: empty input ✓
Extra probes: '+1', '1++2', '1*', '((1+2)', '1+2)', '*', '()', '1 + + 2' — all ParseError ✓
```
**D6 — Tests Green**
```
Ran 48 tests in 0.001s OK (21 lex + 27 parser) ✓
```

View File

@ -0,0 +1,46 @@
# STATUS-eval.md
## DONE
All eval gates D1D5 Adversary-verified PASS. Phase complete.
---
## Gate: D1-D5 CLAIMED → ALL PASS
All gates are implemented and self-verified. Claiming D1D5 together.
### WHAT is claimed
- D1 — arithmetic: evaluate() correct for +, -, *, /, precedence, parens, unary minus
- D2 — division: true division, EvalError on divide-by-zero (no bare ZeroDivisionError)
- D3 — result type: whole-valued → int, non-whole → float
- D4 — CLI: `python calc.py <expr>` prints result and exits 0; errors to stderr, non-zero exit
- D5 — tests green + no regression
### HOW to verify (cold, from fresh clone)
```bash
cd <repo-root>
python -m unittest -q # must be 0 failures (65 tests)
python calc.py "2+3*4" # expect: 14
python calc.py "(2+3)*4" # expect: 20
python calc.py "7/2" # expect: 3.5
python calc.py "4/2" # expect: 2
python calc.py "1/0" # expect: non-zero exit, error on stderr
python calc.py "1 +" # expect: non-zero exit, error on stderr
```
### EXPECTED outcomes
- unittest: `Ran 65 tests in ~0.001s``OK`
- `2+3*4``14`
- `(2+3)*4``20`
- `7/2``3.5`
- `4/2``2`
- `1/0` → exit 1, stderr: `error: division by zero`
- `1 +` → exit 1, stderr: `error: unexpected end of input`
### WHERE inputs live
- `calc/evaluator.py` — evaluator + EvalError
- `calc/test_evaluator.py` — unittest suite covering D1D3
- `calc.py` — CLI entry point
- Commit: (see git log — latest commit on main)

View File

@ -0,0 +1,98 @@
# STATUS-lex.md
## DONE
All DoD gates Adversary-verified PASS. Phase `lex` complete.
- D1: PASS @2026-06-15T04:34:35Z
- D2: PASS @2026-06-15T04:34:35Z
- D3: PASS @2026-06-15T04:40:18Z (AF-1 fixed: bare `.` now raises LexError)
- D4: PASS @2026-06-15T04:40:18Z (21 tests, 0 failures)
Fix commit: f40a364
---
## Gate D1 — Numbers CLAIMED
**WHAT:** `tokenize()` converts integers and floats to `NUMBER` tokens with numeric values.
**HOW to verify:**
```bash
python -c "from calc.lexer import tokenize; r=tokenize('42'); print(r[0].kind, r[0].value, type(r[0].value).__name__)"
python -c "from calc.lexer import tokenize; r=tokenize('3.14'); print(r[0].kind, r[0].value, type(r[0].value).__name__)"
python -c "from calc.lexer import tokenize; r=tokenize('.5'); print(r[0].kind, r[0].value)"
python -c "from calc.lexer import tokenize; r=tokenize('10.'); print(r[0].kind, r[0].value, type(r[0].value).__name__)"
python -c "from calc.lexer import tokenize; print([(t.kind,t.value) for t in tokenize('42')])"
```
**EXPECTED:**
- `NUMBER 42 int`
- `NUMBER 3.14 float`
- `NUMBER 0.5`
- `NUMBER 10.0 float`
- `[('NUMBER', 42), ('EOF', None)]`
**WHERE:** `calc/lexer.py`, commit fb03159
---
## Gate D2 — Operators & Parens CLAIMED
**WHAT:** `+ - * / ( )` tokenize to correct kinds; `"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,t.value) for t in tokenize('+-*/()')] )"
```
**EXPECTED:**
- `['NUMBER', 'PLUS', 'NUMBER', 'STAR', 'NUMBER', 'EOF']`
- `[('PLUS', '+'), ('MINUS', '-'), ('STAR', '*'), ('SLASH', '/'), ('LPAREN', '('), ('RPAREN', ')'), ('EOF', None)]`
**WHERE:** `calc/lexer.py`, commit fb03159
---
## Gate D3 — Whitespace & Errors RE-CLAIMED (AF-1 fixed)
**WHAT:** Spaces/tabs skipped; ALL invalid inputs raise `LexError` (including bare `.`).
**Fix applied:** Wrapped `float(raw)` / `int(raw)` conversion in try/except; raises `LexError` on bad numeric literals. Added `test_bare_dot_raises_lex_error` to test suite.
**HOW to verify:**
```bash
python -m unittest -q
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')"
python -c "from calc.lexer import tokenize; tokenize('.')"
```
**EXPECTED:**
- `Ran 21 tests in 0.000s\nOK`
- `['NUMBER', 'PLUS', 'NUMBER', 'EOF']`
- Raises `calc.lexer.LexError: unexpected character '@' at position 2`
- Raises `calc.lexer.LexError: invalid number literal '.' at position 0`
**WHERE:** `calc/lexer.py`, `calc/test_lexer.py` (see fix commit)
---
## Gate D4 — Tests Green CLAIMED
**WHAT:** `calc/test_lexer.py` passes under `python -m unittest` with 0 failures.
**HOW to verify:**
```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:**
- `Ran 20 tests in 0.000s\nOK`
- `[('NUMBER', 3.5), ('STAR', '*'), ('LPAREN', '('), ('NUMBER', 1), ('MINUS', '-'), ('NUMBER', 2), ('RPAREN', ')'), ('EOF', None)]`
- Raises `LexError`
**WHERE:** `calc/test_lexer.py`, commit fb03159

View File

@ -0,0 +1,126 @@
# STATUS-parse.md
## DONE
All DoD gates Adversary-verified PASS. Phase `parse` complete.
- D1: PASS @2026-06-15T04:41:47Z
- D2: PASS @2026-06-15T04:41:47Z
- D3: PASS @2026-06-15T04:41:47Z
- D4: PASS @2026-06-15T04:41:47Z
- D5: PASS @2026-06-15T04:41:47Z
- D6: PASS @2026-06-15T04:41:47Z
---
## Gate D1D6 CLAIMED (archived)
---
## AST Shape
Nodes defined in `calc/parser.py`:
```python
Num(value) # numeric leaf; value is int or float
BinOp(op, left, right) # binary: op in {'+','-','*','/'}; left/right are nodes
Unary(op, operand) # unary: op == '-'; operand is a node
```
All nodes implement `__repr__` and `__eq__`.
---
## D1 — Precedence
**WHAT:** `*` and `/` bind tighter than `+` and `-`.
**HOW:**
```bash
python -c "from calc.lexer import tokenize; from calc.parser import parse; print(repr(parse(tokenize('1+2*3'))))"
python -c "from calc.lexer import tokenize; from calc.parser import parse; print(repr(parse(tokenize('2*3+4'))))"
```
**EXPECTED:**
- `BinOp('+', Num(1), BinOp('*', Num(2), Num(3)))`
- `BinOp('+', BinOp('*', Num(2), Num(3)), Num(4))`
---
## D2 — Left Associativity
**WHAT:** Same-precedence operators associate left.
**HOW:**
```bash
python -c "from calc.lexer import tokenize; from calc.parser import parse; print(repr(parse(tokenize('8-3-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(3)), Num(2))`
- `BinOp('/', BinOp('/', Num(8), Num(4)), Num(2))`
---
## D3 — Parentheses
**WHAT:** Parens override precedence.
**HOW:**
```bash
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
**WHAT:** Leading and nested unary minus parses correctly.
**HOW:**
```bash
python -c "from calc.lexer import tokenize; from calc.parser import parse; print(repr(parse(tokenize('-5'))))"
python -c "from calc.lexer import tokenize; from calc.parser import parse; print(repr(parse(tokenize('-(1+2)'))))"
python -c "from calc.lexer import tokenize; from calc.parser import parse; print(repr(parse(tokenize('3 * -2'))))"
```
**EXPECTED:**
- `Unary('-', Num(5))`
- `Unary('-', BinOp('+', Num(1), Num(2)))`
- `BinOp('*', Num(3), Unary('-', Num(2)))`
---
## D5 — Errors
**WHAT:** Malformed input raises `ParseError`.
**HOW:**
```bash
python -c "from calc.lexer import tokenize; from calc.parser import parse, ParseError
for s in ['1 +','(1','1 2',')(','']:
try: parse(tokenize(s)); print('NO ERROR for', repr(s))
except ParseError as e: print('OK ParseError:', repr(s), '->', e)
"
```
**EXPECTED:** All five raise `ParseError` (not any other exception type).
---
## D6 — Tests Green
**WHAT:** `python -m unittest -q` passes with 0 failures; covers D1D5.
**HOW:**
```bash
python -m unittest -q
```
**EXPECTED:** `Ran 48 tests in ...s\n\nOK` (21 lex + 27 parser)
**WHERE:** `calc/parser.py`, `calc/test_parser.py`