calculator: add infix parser
This commit is contained in:
parent
425733deca
commit
2ac7ce0520
|
@ -1,2 +1,3 @@
|
||||||
|
from .infix import parse_infix
|
||||||
from .postfix import parse_postfix
|
from .postfix import parse_postfix
|
||||||
from .prefix import parse_prefix
|
from .prefix import parse_prefix
|
||||||
|
|
80
calculator/calculator/parse/infix.py
Normal file
80
calculator/calculator/parse/infix.py
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING, List, Union, cast
|
||||||
|
|
||||||
|
from calculator.ast import BinOp, Constant, UnaryOp
|
||||||
|
from calculator.core import operations
|
||||||
|
|
||||||
|
from .parsed_string import ParsedString
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from calculator.ast import Node
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
E : T [ (+|-) T ]*
|
||||||
|
|
||||||
|
T : F [ (*|/) F ]*
|
||||||
|
|
||||||
|
F : [ (-|+) ]* P
|
||||||
|
|
||||||
|
P : G [ (^) F ]*
|
||||||
|
|
||||||
|
G : '(' E ')' | CONSTANT
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def parse_g(l: List[Union[int, str]]) -> Node:
|
||||||
|
top = l.pop(0)
|
||||||
|
if top == "(":
|
||||||
|
ans = parse_e(l)
|
||||||
|
assert l.pop(0) == ")"
|
||||||
|
return ans
|
||||||
|
return Constant(top)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_p(l: List[Union[int, str]]) -> Node:
|
||||||
|
lhs = parse_g(l)
|
||||||
|
while len(l) and l[0] == "^":
|
||||||
|
op = l.pop(0)
|
||||||
|
rhs = parse_f(l)
|
||||||
|
lhs = BinOp(operations.STR_TO_BIN[op], lhs, rhs)
|
||||||
|
return lhs
|
||||||
|
|
||||||
|
|
||||||
|
def parse_f(l: List[Union[int, str]]) -> Node:
|
||||||
|
str_to_unop = {
|
||||||
|
"+": operations.identity,
|
||||||
|
"-": operations.negate,
|
||||||
|
}
|
||||||
|
if l[0] in str_to_unop:
|
||||||
|
op = l.pop(0)
|
||||||
|
return UnaryOp(str_to_unop[op], parse_f(l))
|
||||||
|
return parse_p(l)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_t(l: List[Union[int, str]]) -> Node:
|
||||||
|
lhs = parse_f(l)
|
||||||
|
while len(l) and l[0] in ["*", "/"]:
|
||||||
|
op = cast(str, l.pop(0))
|
||||||
|
rhs = parse_t(l)
|
||||||
|
lhs = BinOp(operations.STR_TO_BIN[op], lhs, rhs)
|
||||||
|
return lhs
|
||||||
|
|
||||||
|
|
||||||
|
def parse_e(l: List[Union[int, str]]) -> Node:
|
||||||
|
lhs = parse_t(l)
|
||||||
|
while len(l) and l[0] in ["+", "-"]:
|
||||||
|
op = cast(str, l.pop(0))
|
||||||
|
rhs = parse_t(l)
|
||||||
|
lhs = BinOp(operations.STR_TO_BIN[op], lhs, rhs)
|
||||||
|
return lhs
|
||||||
|
|
||||||
|
|
||||||
|
def parse_infix(input: str) -> Node:
|
||||||
|
"""
|
||||||
|
Parses the given string in infix notation.
|
||||||
|
"""
|
||||||
|
parsed = ParsedString(input).tokenize()
|
||||||
|
ans = parse_e(parsed)
|
||||||
|
return ans
|
42
calculator/calculator/parse/test_infix.py
Normal file
42
calculator/calculator/parse/test_infix.py
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
from calculator.ast import BinOp, Constant, UnaryOp
|
||||||
|
from calculator.core import operations
|
||||||
|
|
||||||
|
from .infix import parse_infix
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_constant():
|
||||||
|
assert parse_infix("42") == Constant(42)
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_negated_constant():
|
||||||
|
assert parse_infix("-42") == UnaryOp(operations.negate, Constant(42))
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_doubly_negated_constant():
|
||||||
|
assert parse_infix("--42") == UnaryOp(
|
||||||
|
operations.negate, UnaryOp(operations.negate, Constant(42))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_binary_operation():
|
||||||
|
assert parse_infix("12 + 27") == BinOp(operations.plus, Constant(12), Constant(27))
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_complete_expression_tree():
|
||||||
|
assert parse_infix("(12 + 27 )* (42 - 51)") == BinOp(
|
||||||
|
operations.times,
|
||||||
|
BinOp(operations.plus, Constant(12), Constant(27)),
|
||||||
|
BinOp(operations.minus, Constant(42), Constant(51)),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_power_binds_to_the_right():
|
||||||
|
assert parse_infix("12 ^ 27 ^ 42") == BinOp(
|
||||||
|
operations.pow, Constant(12), BinOp(operations.pow, Constant(27), Constant(42))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_negate_and_pow_dont_mix():
|
||||||
|
assert parse_infix("-12 ^ 27") == UnaryOp(
|
||||||
|
operations.negate, BinOp(operations.pow, Constant(12), Constant(27))
|
||||||
|
)
|
Loading…
Reference in a new issue