From f103adddacbb5f4112e1d91d87d74f2a0d283dec Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Sat, 30 Nov 2019 15:21:42 +0100 Subject: [PATCH] calculator: add an evaluation visitor The `calculator.eval` module defines an `Evaluator` visitor for the AST hierarchy. --- calculator/calculator/__init__.py | 1 + calculator/calculator/eval/__init__.py | 1 + calculator/calculator/eval/evaluator.py | 35 ++++++++++++++++++++ calculator/calculator/eval/test_evaluator.py | 34 +++++++++++++++++++ 4 files changed, 71 insertions(+) create mode 100644 calculator/calculator/eval/__init__.py create mode 100644 calculator/calculator/eval/evaluator.py create mode 100644 calculator/calculator/eval/test_evaluator.py diff --git a/calculator/calculator/__init__.py b/calculator/calculator/__init__.py index ba34e1e..14294ba 100644 --- a/calculator/calculator/__init__.py +++ b/calculator/calculator/__init__.py @@ -1 +1,2 @@ import calculator.ast +import calculator.eval diff --git a/calculator/calculator/eval/__init__.py b/calculator/calculator/eval/__init__.py new file mode 100644 index 0000000..6eedafc --- /dev/null +++ b/calculator/calculator/eval/__init__.py @@ -0,0 +1 @@ +from .evaluator import Evaluator diff --git a/calculator/calculator/eval/evaluator.py b/calculator/calculator/eval/evaluator.py new file mode 100644 index 0000000..033aeec --- /dev/null +++ b/calculator/calculator/eval/evaluator.py @@ -0,0 +1,35 @@ +from __future__ import annotations + +import dataclasses +from typing import TYPE_CHECKING + +from calculator.ast.visit import Visitor +from pydantic.dataclasses import dataclass + +if TYPE_CHECKING: + from calculator.ast import BinOp, Constant, Node, UnaryOp + + +@dataclass +class Evaluator(Visitor): + """ + Evaluate a Tree and retrieve the calculated value. + """ + + value: int = dataclasses.field(default=0, init=False) + + def visit_and_get_value(self, n: Node) -> int: + n.accept(self) + return self.value + + def visit_constant(self, c: Constant) -> None: + self.value = c.value + + def visit_binop(self, b: BinOp) -> None: + lhs_val = self.visit_and_get_value(b.lhs) + rhs_val = self.visit_and_get_value(b.rhs) + self.value = b.op(lhs_val, rhs_val) + + def visit_unaryop(self, b: UnaryOp) -> None: + rhs_val = self.visit_and_get_value(b.rhs) + self.value = b.op(rhs_val) diff --git a/calculator/calculator/eval/test_evaluator.py b/calculator/calculator/eval/test_evaluator.py new file mode 100644 index 0000000..6aa866f --- /dev/null +++ b/calculator/calculator/eval/test_evaluator.py @@ -0,0 +1,34 @@ +import pytest +from calculator.ast import BinOp, Constant, UnaryOp + +from .evaluator import Evaluator + + +@pytest.fixture +def vis(): + return Evaluator() + + +def test_evaluate_constant(vis): + c = Constant(42) + assert vis.visit_and_get_value(c) == 42 + + +def test_evaluate_unaryop_identity(vis): + op = UnaryOp(lambda x: x, Constant(42)) + assert vis.visit_and_get_value(op) == 42 + + +def test_evaluate_unaryop_negate(vis): + op = UnaryOp(lambda x: -x, Constant(-42)) + assert vis.visit_and_get_value(op) == 42 + + +def test_evaluate_binop_plus(vis): + op = BinOp(lambda x, y: x + y, Constant(12), Constant(27)) + assert vis.visit_and_get_value(op) == 39 + + +def test_evaluate_binop_minus(vis): + op = BinOp(lambda x, y: x - y, Constant(42), Constant(51)) + assert vis.visit_and_get_value(op) == -9