Source code for at.load.rpn

"""RPN interpreter. From Onel Harrison:
https://onelharrison.medium.com/watch-building-a-reverse-polish-notation-rpn-evaluator-in-python-75b1c910fab6.
"""

from __future__ import annotations

__all__ = ["Rpn"]

import operator as op
from math import sqrt, sin, cos, pi, pow
from typing import Any

Number = int | float | str


# noinspection PyUnusedLocal
def _pop(x):
    return []


def _swap(x, y):
    return [y, x]


supported_operators = {
    "+": (op.add, 2),
    "-": (op.sub, 2),
    "*": (op.mul, 2),
    "/": (op.truediv, 2),
    "sqrt": (sqrt, 1),
    "pi": (pi, 0),
    "sin": (sin, 1),
    "cos": (cos, 1),
    "pow": (pow, 2),
    "pop": (_pop, 1),
    "swap": (_swap, 2),
}


[docs] class Rpn: def __init__(self): self.stack = [] self.store = False self.operators = supported_operators.copy() def _mpop(self, n: int = 1) -> list[Any]: """Pops and returns `n` items from the stack.""" try: return [self.stack.pop() for _ in range(n)] except IndexError: msg = "RPN: Malformed expression: empty stack" raise SyntaxError(msg) from None @staticmethod def _to_num(x: Any) -> Number: """Converts a value to its appropriate numeric type.""" try: n = float(x) except ValueError: return x else: return int(n) if n.is_integer() else n def _consume_token(self, token: str) -> None: """Consumes a token given the current stack and returns the updated stack.""" if token == "sto": self.store = True return elif self.store: self.operators[token] = (self.stack[-1], 0) self.store = False return try: oper, narg = self.operators[token] except KeyError: result = self._to_num(token) else: result = oper if narg == 0 else oper(*reversed(self._mpop(narg))) if isinstance(result, list): self.stack.extend(result) else: self.stack.append(result)
[docs] def input(self, expr: str) -> None: for token in expr.split(): self._consume_token(token)
[docs] def evaluate(self, expr: str) -> Number: """Evaluate an RPN expression and return the result.""" for token in expr.split(): self._consume_token(token) (result,) = self._mpop(1) if self.stack: msg = "RPN: Found extra tokens" raise SyntaxError(msg) return result