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 .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