calculator: add infix parser

This commit is contained in:
Bruno BELANYI 2019-11-30 18:06:07 +01:00
parent 425733deca
commit 2ac7ce0520
3 changed files with 123 additions and 0 deletions

View File

@ -1,2 +1,3 @@
from .infix import parse_infix
from .postfix import parse_postfix
from .prefix import parse_prefix

View 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

View 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))
)