You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
129 lines
4.0 KiB
129 lines
4.0 KiB
from chatbug.matheval import lexer
|
|
from chatbug.matheval.lexer import Token
|
|
|
|
|
|
class Statement:
|
|
pass
|
|
|
|
class Expression(Statement):
|
|
def __init__(self, value: str):
|
|
self.value = value
|
|
|
|
class Equation:
|
|
def __init__(self, lhs: Expression, rhs: Expression):
|
|
self.lhs = lhs
|
|
self.rhs = rhs
|
|
|
|
class Solve(Statement):
|
|
def __init__(self, equations: list[Equation], variables: list[Expression]):
|
|
self.equations = equations
|
|
self.variables = variables
|
|
|
|
|
|
|
|
|
|
class Parser:
|
|
def __init__(self):
|
|
self.tokens: list[Token] # tokens from lexer
|
|
self._last_eaten = None
|
|
|
|
def not_eof(self) -> bool:
|
|
return self.tokens[0].type is not lexer.END_OF_INPUT
|
|
|
|
def at(self) -> Token:
|
|
return self.tokens[0]
|
|
|
|
def at_last(self) -> Token:
|
|
return self._last_eaten
|
|
|
|
def eat(self) -> Token:
|
|
self._last_eaten = self.tokens.pop(0)
|
|
return self._last_eaten
|
|
|
|
def backtrack(self):
|
|
if not self._last_eaten:
|
|
raise Exception("Cannot backtrack.")
|
|
self.tokens.insert(0, self._last_eaten)
|
|
self._last_eaten = None
|
|
|
|
def eat_expect(self, token_type: int | str) -> Token:
|
|
prev = self.eat()
|
|
if prev.type is not token_type:
|
|
raise Exception("expected to consume '%s' but '%s' encountered." % (str(token_type), str(prev.type)))
|
|
return prev
|
|
|
|
def at_expect(self, token_type: int | str) -> Token:
|
|
prev = self.at()
|
|
if prev.type is not token_type:
|
|
raise Exception("expected to be at '%s' but '%s' encountered." % (str(token_type), str(prev.type)))
|
|
return prev
|
|
|
|
def parse(self, tokens: list[Token]) -> Statement:
|
|
self.tokens = tokens
|
|
statement = self.parse_statement()
|
|
self.at_expect(lexer.END_OF_INPUT)
|
|
return statement
|
|
|
|
def parse_statement(self) -> Statement:
|
|
type = self.at().type
|
|
if type is lexer.SOLVE:
|
|
return self.parse_solve()
|
|
return self.parse_expression(merge_commas=True)
|
|
|
|
def parse_solve(self) -> Solve:
|
|
"""
|
|
solve x = 1 for x
|
|
solve x = y and y = 2 for x and y
|
|
"""
|
|
self.eat_expect(lexer.SOLVE)
|
|
equations = [] # list of equations
|
|
variables = [] # list of variables to solve for
|
|
|
|
while self.not_eof() and self.at().type is not lexer.FOR:
|
|
equations.append(self.parse_equation())
|
|
selfattype = self.at().type
|
|
if selfattype is lexer.AND or selfattype is lexer.COMMA:
|
|
self.eat()
|
|
|
|
self.eat_expect(lexer.FOR)
|
|
|
|
while self.not_eof():
|
|
variables.append(self.parse_expression(merge_commas=False))
|
|
selfattype = self.at().type
|
|
if selfattype is lexer.AND or selfattype is lexer.COMMA:
|
|
self.eat()
|
|
|
|
return Solve(equations, variables)
|
|
|
|
def parse_equation(self) -> Equation:
|
|
lhs = self.parse_expression(merge_commas=False)
|
|
self.eat_expect(lexer.EQUALS)
|
|
rhs = self.parse_expression(merge_commas=False)
|
|
return Equation(lhs, rhs)
|
|
|
|
def parse_expression(self, merge_commas) -> Expression:
|
|
"""
|
|
math expression
|
|
e.g:
|
|
sin(45) / 4 * pi
|
|
"""
|
|
|
|
if merge_commas == True:
|
|
values = []
|
|
while self.not_eof():
|
|
token = self.eat()
|
|
if token.type is lexer.COMMA:
|
|
values.append(lexer.COMMA)
|
|
elif token.type is lexer.EQUALS:
|
|
values.append(lexer.EQUALS)
|
|
else:
|
|
values.append(token.value)
|
|
# token = self.eat_expect(lexer.EXPRESSION)
|
|
# values.append(token.value)
|
|
# if self.at() is lexer.COMMA:
|
|
# token = self.eat()
|
|
# values.append(lexer.COMMA)
|
|
return Expression("".join(values))
|
|
else:
|
|
token = self.eat_expect(lexer.EXPRESSION)
|
|
return Expression(token.value)
|