105 lines
3.2 KiB
Python

from .expression import BinaryExpression, Expression, UnaryExpression, ValueExpression
from .operation import (
BraceOperation,
FunctionOperation,
Operation,
OperatorOperation,
priorities,
)
from .tokenizer import Token, Tokenizer
from .types import ValueType
from .constants import CONSTANTS
class Parser:
"""
Class that accepts math expression in its constructor,
parses it and provides `evaluate` method to substitue
variables values to it
"""
def __init__(self, input_expr: str):
self.input_expr = input_expr
self.variables_names: set[str] = set()
self._tokenize()
self._parse()
def _tokenize(self):
"""
Uses `Tokenizer` class for math expression splitting
"""
self.tokens = Tokenizer(self.input_expr)
def _parse(self):
"""
Generates an evaluation tree from tokens by utilizing two
stacks - one for values and one for operations. Values and
operations are placed into corresponding stacks, and when
possible, assempled into tree node.
"""
self.val_stack: list[Expression] = []
self.op_stack: list[Operation] = []
for t_val, t_type in self.tokens:
if t_type in (Token.Number, Token.Variable):
self.val_stack.append(ValueExpression(t_val))
if t_type == Token.Variable:
self.variables_names.add(t_val)
elif t_type == Token.Function:
self.op_stack.append(FunctionOperation(t_val))
elif t_type == Token.LBrace:
self.op_stack.append(BraceOperation("("))
elif t_type == Token.RBrace:
while len(self.op_stack) > 0 and not (
self.op_stack[-1].size == 0 and self.op_stack[-1].priority == 0
): # until next in stack is lbrace
self._do_one()
self.op_stack.pop() # pop lbrace
elif t_type == Token.Operator:
t_priority = priorities[t_val]
while (
len(self.op_stack) > 0 and self.op_stack[-1].priority > t_priority
):
self._do_one()
self.op_stack.append(OperatorOperation(t_val))
while len(self.op_stack) > 0:
self._do_one()
self._evaluator = self.val_stack[0].evaluate
self.__debug_expr = repr(self.val_stack)
def _do_one(self):
"""
Assembles one operation into `Expression` tree node that is stored
on value stack.
"""
op = self.op_stack.pop()
if op.size == 1:
a = self.val_stack.pop()
self.val_stack.append(UnaryExpression(op.evaluator, a))
elif op.size == 2:
b, a = self.val_stack.pop(), self.val_stack.pop() # inversed pop order
self.val_stack.append(BinaryExpression(op.evaluator, a, b))
def evaluate(self, variables: dict[str, ValueType]):
"""
Evaluates supplied to constructor expression with provided
variables values
"""
variables |= CONSTANTS
return self._evaluator(variables)
def __repr__(self):
return self.__debug_expr