From 17803fd3a47bd7a5cd4b59a9acc01cb4ae25dfce Mon Sep 17 00:00:00 2001 From: beam Date: Tue, 3 Nov 2026 17:24:46 +0000 Subject: [PATCH] beam: fix unary-vs-power precedence in expr_eval (-2**2 now -(2**2)) --- wizard-work/beam/expr_eval.py | 3 +- wizard-work/beam/test_expr_eval.py | 56 ++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 wizard-work/beam/test_expr_eval.py diff --git a/wizard-work/beam/expr_eval.py b/wizard-work/beam/expr_eval.py index 338621b..97e787f 100644 --- a/wizard-work/beam/expr_eval.py +++ b/wizard-work/beam/expr_eval.py @@ -111,7 +111,8 @@ class Parser: def parse_unary(self): if self.peek()[0] == 'op' and self.peek()[1] in ('+', '-'): op = self.advance()[1] - v = self.parse_unary() + # operand is a full power expression so that -2**2 == -(2**2) + v = self.parse_factor() return -v if op == '-' else v return self.parse_primary() diff --git a/wizard-work/beam/test_expr_eval.py b/wizard-work/beam/test_expr_eval.py new file mode 100644 index 0000000..b30b71a --- /dev/null +++ b/wizard-work/beam/test_expr_eval.py @@ -0,0 +1,56 @@ +import math +import sys, os +sys.path.insert(0, os.path.dirname(__file__)) +from expr_eval import evaluate, EvalError + +def test_basic(): + assert evaluate("2 + 3 * 4") == 14 + assert evaluate("(2+3)*4") == 20 + assert evaluate("10 / 3") == 10/3 + assert evaluate("10 // 3") == 3 + assert evaluate("10 % 3") == 1 + +def test_power_precedence(): + # unary binds looser than ** (matches Python) + assert evaluate("-2**2") == -4 + assert evaluate("(-2)**2") == 4 + assert evaluate("2**-2") == 0.25 + assert evaluate("2**3**2") == 512 + +def test_unary(): + assert evaluate("--5") == 5 + assert evaluate("-(-3)") == 3 + +def test_funcs(): + assert evaluate("sqrt(16)") == 4 + assert abs(evaluate("sin(0)")) < 1e-12 + assert evaluate("max(1,2,3)") == 3 + assert evaluate("min(1,2,3)") == 1 + assert evaluate("abs(-7)") == 7 + +def test_vars(): + assert evaluate("x + pi", {"x": 3, "pi": math.pi}) == 3 + math.pi + +def test_errors(): + for e in ["2 +", "1/0", "foo", "2 @ 3", "sqrt(-1)"]: + try: + evaluate(e) + raise AssertionError(f"no error for {e!r}") + except EvalError: + pass + +if __name__ == "__main__": + # run all test_ functions + import traceback + g = globals() + failed = 0 + for name in sorted(n for n in g if n.startswith("test_")): + try: + g[name]() + print("PASS", name) + except Exception: + failed += 1 + print("FAIL", name) + traceback.print_exc() + print("FAILED:", failed) + sys.exit(1 if failed else 0)