/RestoredSensitiveExemption
Created 2 years ago...
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
from __future__ import annotations
from typing import ClassVar, NoReturn, Optional, Callable, Any, TYPE_CHECKING
from decimal import Decimal
import inspect
from rply import Token, ParserGenerator
from rply.lexer import SourcePosition, LexingError
from .lexer import LexerGenerator
from .ast import *
if TYPE_CHECKING:
from rply.lexer import Lexer
from rply.parser import LRParser
__all__ = ('Parser',)
class Parser:
lg: ClassVar[LexerGenerator] = LexerGenerator()
pg: ClassVar[ParserGenerator] = ParserGenerator(
[
token.name for token in lg.rules
],
precedence=[
('right', ['UNOP']),
('left', ['ADD', 'SUB']),
('left', ['MUL', 'DIV', 'MOD']),
('left', ['IMPL_MUL']),
('right', ['POW']),
('left', ['FAC']),
]
)
def __init__(
self, /,
*,
functions: Optional[dict[str, Callable[..., Any]]] = None,
constants: Optional[dict[str, Decimal | int]] = None,
) -> None:
self.functions = {} # TODO
self.constants = {} # TODO
self.variables: list[Variable] = []
self._lexer: Lexer = self.lg.build()
self._parser: LRParser = self.pg.build()
def parse(self, equation: str) -> Conditional:
try:
return self._parser.parse(
self._lexer.lex(equation),
state=self
) # type: ignore
except LexingError as e:
pos: SourcePosition = e.getsourcepos()
raise SyntaxError(f"Invalid token {e.message or ''} @ {pos.lineno}:{pos.colno}")
@staticmethod
@pg.production('equation : expr')
@pg.production('equation : expr EQ expr')
@pg.production('equation : expr FAC EQ expr')
@pg.production('equation : expr LT expr')
@pg.production('equation : expr LT EQ expr')
@pg.production('equation : expr GT expr')
@pg.production('equation : expr GT EQ expr')
def equation(_, p: list[Token], /) -> BinaryOp:
if len(p) == 1 and isinstance(p[0], Ast):
return Eq(p[0], Number('0'))
return {
('EQ', None): Eq,
('FAC', 'EQ'): Ne,
('LT', None): Lt,
('LT', 'EQ'): Le,
('GT', None): Gt,
('GT', 'EQ'): Ge,
}[(p[1].gettokentype(), getattr(p[2], 'gettokentype', lambda: None)())](p[0], p[-1])
@staticmethod
@pg.production('expr : left_group group', precedence='IMPL_MUL')
@pg.production('group : left_group group', precedence='IMPL_MUL')
@pg.production('expr : group group', precedence='IMPL_MUL')
@pg.production('group : group group', precedence='IMPL_MUL')
def implicit_mul(_, p: list[Ast], /) -> Mul:
return Mul(p[0], p[1])
@staticmethod
@pg.production('group : NUMBER')
def number(_, p: list[Token], /) -> Number:
return Number(p[0].getstr())
@staticmethod
@pg.production('group : LBRACK expr RBRACK')
@pg.production('group : LBRACE expr RBRACE')
def paren(_, p: list[Ast], /) -> Ast:
return p[1]
@staticmethod
@pg.production('group : expr FAC')
def factorial(_, p: list[Token], /) -> Fac:
return Fac(p[0].getstr())
@staticmethod
@pg.production('expr : expr ADD expr')
#@pg.production('expr : group ADD expr')
#@pg.production('expr : expr ADD group')
@pg.production('expr : expr SUB expr')
@pg.production('expr : group SUB expr')
@pg.production('expr : expr SUB group')
@pg.production('expr : expr MUL expr')
@pg.production('expr : group MUL expr')
@pg.production('expr : expr MUL group')
@pg.production('expr : expr DIV expr')
@pg.production('expr : group DIV expr')
@pg.production('expr : expr DIV group')
@pg.production('expr : expr MOD expr')
@pg.production('expr : group MOD expr')
@pg.production('expr : expr MOD group')
@pg.production('group : expr POW expr')
@pg.production('group : group POW expr')
@pg.production('group : expr POW group')
@pg.production('group : group POW group')
def binop(_, p: list[Token], /) -> BinaryOp:
return {
'ADD': Add,
'SUB': Sub,
'MUL': Mul,
'DIV': Div,
'MOD': Mod,
'POW': Pow,
}[p[1].gettokentype()](p[0], p[2])
@staticmethod
@pg.production('group : IDENT LBRACK expr RBRACK', precedence='FAC')
@pg.production('group : IDENT SUBSCRIPT group LBRACK expr RBRACK', precedence='FAC')
def fx(state: Parser, p: list[Ast], /) -> Function | Mul:
assert isinstance(p[0], Token)
ident = p[0].getstr()
if f := state.functions.get(ident):
arguments = (p[2],)
if len(p) == 6:
arguments += (p[-2],)
return Function(f, *arguments)
return Mul(Variable(ident), p[-2])
@staticmethod
@pg.production('group : IDENT', precedence='FAC')
def variable(state: Parser, p: list[Token], /) -> Constant | Variable | Mul | Function:
ident = p[0].getstr()
if len(p) == 3:
ident += f'_{p[-1].getstr()}'
if x := state.constants.get(ident):
return Constant(x)
if len(ident) == 1 or len(p) == 3:
var = Variable(ident)
state.variables.append(var)
return var
variables = map(Variable, ident)
expr = Mul(next(variables), next(variables))
for x in variables:
if con := state.constants.get(ident):
x = Constant(con)
else:
state.variables.append(x)
expr = Mul(expr, x)
return expr
@staticmethod
@pg.production('expr : group')
# @pg.production('expr : left_group')
def expr_group(_, p: list[Ast], /) -> Ast:
return p[0]
@staticmethod
#@pg.production("left_group : ADD group", precedence='UNOP')
@pg.production("left_group : SUB group")
#@pg.production("left_group : ADD left_group", precedence='UNOP')
@pg.production("left_group : SUB left_group")
def unop(_, p: list[Token], /) -> UnaryOp:
return {
'ADD': Pos,
'SUB': Neg,
}[p[0].gettokentype()](p[1])
@staticmethod
@pg.error
def error_handler(_, token: Token) -> NoReturn:
pos: Optional[SourcePosition] = token.getsourcepos()
raise ValueError(
f"Encountered a {token.gettokentype()}: '{token.getstr()}' "
f"@ {getattr(pos, 'lineno', 'x')}:{getattr(pos, 'colno', 'x')} where it was not expected"
)