pymu-eval 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pymu_eval/__init__.py +75 -0
- pymu_eval/bytecode.py +383 -0
- pymu_eval/definitions.py +227 -0
- pymu_eval/errors.py +181 -0
- pymu_eval/math_impl.py +630 -0
- pymu_eval/parser.py +1728 -0
- pymu_eval/token_reader.py +924 -0
- pymu_eval-0.0.1.dist-info/METADATA +124 -0
- pymu_eval-0.0.1.dist-info/RECORD +12 -0
- pymu_eval-0.0.1.dist-info/WHEEL +5 -0
- pymu_eval-0.0.1.dist-info/licenses/LICENSE +201 -0
- pymu_eval-0.0.1.dist-info/top_level.txt +1 -0
pymu_eval/__init__.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pymu-eval (pymu_eval): Python implementation of the muParser mathematical expression parser
|
|
3
|
+
with native support for scalars, NumPy ndarrays, xarray DataArrays, and xarray Datasets.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .bytecode import ParserByteCode, SToken
|
|
7
|
+
from .definitions import (
|
|
8
|
+
CmdCode,
|
|
9
|
+
DEFAULT_OPRT,
|
|
10
|
+
ECmdCode,
|
|
11
|
+
EErrorCodes,
|
|
12
|
+
EOprtAssociativity,
|
|
13
|
+
EOprtPrecedence,
|
|
14
|
+
EParserVersionInfo,
|
|
15
|
+
ESynCodes,
|
|
16
|
+
ETypeCode,
|
|
17
|
+
ErrorCode,
|
|
18
|
+
MaxLenExpression,
|
|
19
|
+
MaxLenIdentifier,
|
|
20
|
+
MaxNestingDepth,
|
|
21
|
+
OprtAssociativity,
|
|
22
|
+
OprtPrecedence,
|
|
23
|
+
PUBLIC_ENUMS,
|
|
24
|
+
ParserVersion,
|
|
25
|
+
ParserVersionDate,
|
|
26
|
+
TypeCode,
|
|
27
|
+
)
|
|
28
|
+
from .errors import ERROR_MESSAGES, ParserError
|
|
29
|
+
from .math_impl import MathImpl, ParserIntMathImpl
|
|
30
|
+
from .parser import Parser, ParserBase, ParserInt, eval_expr, evaluate
|
|
31
|
+
from .token_reader import ParserCallback, ParserToken, ParserTokenReader, Variable, VariableRef
|
|
32
|
+
|
|
33
|
+
# Export enum members at package level for C++-style compatibility
|
|
34
|
+
for _enum_cls in PUBLIC_ENUMS:
|
|
35
|
+
globals().update(_enum_cls.__members__)
|
|
36
|
+
|
|
37
|
+
__version__ = "2.3.6"
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
"Parser",
|
|
41
|
+
"ParserBase",
|
|
42
|
+
"ParserInt",
|
|
43
|
+
"ParserError",
|
|
44
|
+
"ParserByteCode",
|
|
45
|
+
"SToken",
|
|
46
|
+
"ParserCallback",
|
|
47
|
+
"ParserToken",
|
|
48
|
+
"ParserTokenReader",
|
|
49
|
+
"Variable",
|
|
50
|
+
"VariableRef",
|
|
51
|
+
"MathImpl",
|
|
52
|
+
"ParserIntMathImpl",
|
|
53
|
+
"eval_expr",
|
|
54
|
+
"evaluate",
|
|
55
|
+
"ECmdCode",
|
|
56
|
+
"CmdCode",
|
|
57
|
+
"ETypeCode",
|
|
58
|
+
"TypeCode",
|
|
59
|
+
"EParserVersionInfo",
|
|
60
|
+
"EOprtAssociativity",
|
|
61
|
+
"OprtAssociativity",
|
|
62
|
+
"EOprtPrecedence",
|
|
63
|
+
"OprtPrecedence",
|
|
64
|
+
"ESynCodes",
|
|
65
|
+
"EErrorCodes",
|
|
66
|
+
"ErrorCode",
|
|
67
|
+
"ERROR_MESSAGES",
|
|
68
|
+
"DEFAULT_OPRT",
|
|
69
|
+
"MaxLenExpression",
|
|
70
|
+
"MaxLenIdentifier",
|
|
71
|
+
"MaxNestingDepth",
|
|
72
|
+
"ParserVersion",
|
|
73
|
+
"ParserVersionDate",
|
|
74
|
+
"__version__",
|
|
75
|
+
]
|
pymu_eval/bytecode.py
ADDED
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Bytecode representation and optimizer for pymu-eval.
|
|
3
|
+
Mirrors muParserBytecode.h and muParserBytecode.cpp.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
# pylint: disable=invalid-name
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from typing import Any, Callable, Optional
|
|
12
|
+
|
|
13
|
+
from .definitions import ECmdCode, EErrorCodes
|
|
14
|
+
from .errors import ParserError
|
|
15
|
+
from .math_impl import MathImpl, is_array_like
|
|
16
|
+
from .token_reader import ParserCallback, VariableRef
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class SToken: # pylint: disable=too-many-instance-attributes
|
|
21
|
+
"""
|
|
22
|
+
Single bytecode instruction in the RPN stream.
|
|
23
|
+
Mirrors mu::SToken.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
Cmd: ECmdCode = ECmdCode.cmUNKNOWN
|
|
27
|
+
# Value / Variable fields
|
|
28
|
+
ptr: Optional[VariableRef] = None
|
|
29
|
+
data: Any = 0.0
|
|
30
|
+
data2: Any = 0.0
|
|
31
|
+
# Operator / Jump fields
|
|
32
|
+
offset: int = 0
|
|
33
|
+
# Function fields
|
|
34
|
+
argc: int = 0
|
|
35
|
+
idx: int = 0
|
|
36
|
+
cb: Optional[ParserCallback] = None
|
|
37
|
+
|
|
38
|
+
def copy(self) -> SToken:
|
|
39
|
+
"""Return a shallow copy of this bytecode instruction."""
|
|
40
|
+
return SToken(
|
|
41
|
+
Cmd=self.Cmd,
|
|
42
|
+
ptr=self.ptr,
|
|
43
|
+
data=self.data,
|
|
44
|
+
data2=self.data2,
|
|
45
|
+
offset=self.offset,
|
|
46
|
+
argc=self.argc,
|
|
47
|
+
idx=self.idx,
|
|
48
|
+
cb=self.cb,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
_FOLD_OP_MAP: dict[ECmdCode, Callable[[Any, Any], Any]] = {
|
|
53
|
+
ECmdCode.cmLAND: MathImpl.LAND,
|
|
54
|
+
ECmdCode.cmLOR: MathImpl.LOR,
|
|
55
|
+
ECmdCode.cmLT: MathImpl.LT,
|
|
56
|
+
ECmdCode.cmGT: MathImpl.GT,
|
|
57
|
+
ECmdCode.cmLE: MathImpl.LE,
|
|
58
|
+
ECmdCode.cmGE: MathImpl.GE,
|
|
59
|
+
ECmdCode.cmNEQ: MathImpl.NEQ,
|
|
60
|
+
ECmdCode.cmEQ: MathImpl.EQ,
|
|
61
|
+
ECmdCode.cmADD: lambda a, b: a + b,
|
|
62
|
+
ECmdCode.cmSUB: lambda a, b: a - b,
|
|
63
|
+
ECmdCode.cmMUL: lambda a, b: a * b,
|
|
64
|
+
ECmdCode.cmPOW: MathImpl.Pow,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
_POW_CMD_MAP: dict[int, ECmdCode] = {
|
|
68
|
+
1: ECmdCode.cmVAR,
|
|
69
|
+
2: ECmdCode.cmVARPOW2,
|
|
70
|
+
3: ECmdCode.cmVARPOW3,
|
|
71
|
+
4: ECmdCode.cmVARPOW4,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class ParserByteCode:
|
|
76
|
+
"""
|
|
77
|
+
Bytecode storage and constant/polynomial optimizer.
|
|
78
|
+
Mirrors mu::ParserByteCode.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
def __init__(self) -> None:
|
|
82
|
+
self._stack_pos: int = 0
|
|
83
|
+
self._max_stack_size: int = 0
|
|
84
|
+
self._rpn: list[SToken] = []
|
|
85
|
+
self._enable_optimizer: bool = True
|
|
86
|
+
self._string_buffer: list[str] = []
|
|
87
|
+
self._expr: str = ""
|
|
88
|
+
|
|
89
|
+
def copy(self) -> ParserByteCode:
|
|
90
|
+
"""Create a deep copy of this bytecode sequence."""
|
|
91
|
+
bc = ParserByteCode()
|
|
92
|
+
# pylint: disable=protected-access
|
|
93
|
+
bc._stack_pos = self._stack_pos
|
|
94
|
+
bc._max_stack_size = self._max_stack_size
|
|
95
|
+
bc._rpn = [t.copy() for t in self._rpn]
|
|
96
|
+
bc._enable_optimizer = self._enable_optimizer
|
|
97
|
+
bc._string_buffer = list(self._string_buffer)
|
|
98
|
+
bc._expr = self._expr
|
|
99
|
+
return bc
|
|
100
|
+
|
|
101
|
+
def is_optimizer_enabled(self) -> bool:
|
|
102
|
+
"""Return True if constant folding / algebraic optimization is enabled."""
|
|
103
|
+
return self._enable_optimizer
|
|
104
|
+
|
|
105
|
+
def enable_optimizer(self, stat: bool = True) -> None:
|
|
106
|
+
"""Enable or disable bytecode optimization."""
|
|
107
|
+
self._enable_optimizer = stat
|
|
108
|
+
|
|
109
|
+
def add_var(self, var_ref: VariableRef) -> None:
|
|
110
|
+
"""Emit a variable load instruction."""
|
|
111
|
+
self._stack_pos += 1
|
|
112
|
+
self._max_stack_size = max(self._max_stack_size, self._stack_pos)
|
|
113
|
+
self._rpn.append(
|
|
114
|
+
SToken(
|
|
115
|
+
Cmd=ECmdCode.cmVAR,
|
|
116
|
+
ptr=var_ref,
|
|
117
|
+
data=1.0,
|
|
118
|
+
data2=0.0,
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
def add_val(self, val: Any) -> None:
|
|
123
|
+
"""Emit a constant value load instruction."""
|
|
124
|
+
self._stack_pos += 1
|
|
125
|
+
self._max_stack_size = max(self._max_stack_size, self._stack_pos)
|
|
126
|
+
self._rpn.append(
|
|
127
|
+
SToken(
|
|
128
|
+
Cmd=ECmdCode.cmVAL,
|
|
129
|
+
ptr=None,
|
|
130
|
+
data=0.0,
|
|
131
|
+
data2=val,
|
|
132
|
+
)
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
def _constant_folding(self, oprt: ECmdCode) -> bool:
|
|
136
|
+
sz = len(self._rpn)
|
|
137
|
+
x = self._rpn[sz - 2].data2
|
|
138
|
+
y = self._rpn[sz - 1].data2
|
|
139
|
+
|
|
140
|
+
if oprt in _FOLD_OP_MAP:
|
|
141
|
+
self._rpn[sz - 2].data2 = _FOLD_OP_MAP[oprt](x, y)
|
|
142
|
+
self._rpn.pop()
|
|
143
|
+
return True
|
|
144
|
+
|
|
145
|
+
if oprt == ECmdCode.cmDIV:
|
|
146
|
+
# Leave division by zero to runtime evaluation so it produces inf or nan
|
|
147
|
+
if not is_array_like(y) and y == 0:
|
|
148
|
+
return False
|
|
149
|
+
self._rpn[sz - 2].data2 = MathImpl.Div(x, y)
|
|
150
|
+
self._rpn.pop()
|
|
151
|
+
return True
|
|
152
|
+
|
|
153
|
+
return False
|
|
154
|
+
|
|
155
|
+
def _opt_pow(self, tok_prev: SToken, tok_last: SToken) -> bool:
|
|
156
|
+
if tok_prev.Cmd != ECmdCode.cmVAR or tok_last.Cmd != ECmdCode.cmVAL:
|
|
157
|
+
return False
|
|
158
|
+
exp_val = tok_last.data2
|
|
159
|
+
if is_array_like(exp_val):
|
|
160
|
+
return False
|
|
161
|
+
if exp_val == 0 and (tok_prev.ptr is None or not is_array_like(tok_prev.ptr.get())):
|
|
162
|
+
tok_prev.Cmd = ECmdCode.cmVAL
|
|
163
|
+
tok_prev.ptr = None
|
|
164
|
+
tok_prev.data = 0.0
|
|
165
|
+
tok_prev.data2 = 1.0
|
|
166
|
+
self._rpn.pop()
|
|
167
|
+
return True
|
|
168
|
+
if exp_val in (1, 2, 3, 4):
|
|
169
|
+
tok_prev.Cmd = _POW_CMD_MAP[int(exp_val)]
|
|
170
|
+
self._rpn.pop()
|
|
171
|
+
return True
|
|
172
|
+
return False
|
|
173
|
+
|
|
174
|
+
def _opt_add_sub(self, oprt: ECmdCode, tok_prev: SToken, tok_last: SToken) -> bool:
|
|
175
|
+
var_cmds = (ECmdCode.cmVAR, ECmdCode.cmVARMUL)
|
|
176
|
+
cond = (
|
|
177
|
+
(tok_last.Cmd in var_cmds and tok_prev.Cmd == ECmdCode.cmVAL)
|
|
178
|
+
or (tok_last.Cmd == ECmdCode.cmVAL and tok_prev.Cmd in var_cmds)
|
|
179
|
+
or (
|
|
180
|
+
tok_last.Cmd in var_cmds
|
|
181
|
+
and tok_prev.Cmd in var_cmds
|
|
182
|
+
and tok_prev.ptr is tok_last.ptr
|
|
183
|
+
)
|
|
184
|
+
)
|
|
185
|
+
if not cond:
|
|
186
|
+
return False
|
|
187
|
+
sign = -1.0 if oprt == ECmdCode.cmSUB else 1.0
|
|
188
|
+
tok_prev.Cmd = ECmdCode.cmVARMUL
|
|
189
|
+
tok_prev.ptr = tok_prev.ptr if tok_prev.ptr is not None else tok_last.ptr
|
|
190
|
+
tok_prev.data2 = tok_prev.data2 + sign * tok_last.data2
|
|
191
|
+
tok_prev.data = tok_prev.data + sign * tok_last.data
|
|
192
|
+
self._rpn.pop()
|
|
193
|
+
return True
|
|
194
|
+
|
|
195
|
+
def _opt_mul(self, tok_prev: SToken, tok_last: SToken) -> bool:
|
|
196
|
+
if (tok_last.Cmd == ECmdCode.cmVAR and tok_prev.Cmd == ECmdCode.cmVAL) or (
|
|
197
|
+
tok_last.Cmd == ECmdCode.cmVAL and tok_prev.Cmd == ECmdCode.cmVAR
|
|
198
|
+
):
|
|
199
|
+
tok_prev.Cmd = ECmdCode.cmVARMUL
|
|
200
|
+
tok_prev.ptr = tok_prev.ptr if tok_prev.ptr is not None else tok_last.ptr
|
|
201
|
+
tok_prev.data = tok_prev.data2 + tok_last.data2
|
|
202
|
+
tok_prev.data2 = 0.0
|
|
203
|
+
self._rpn.pop()
|
|
204
|
+
return True
|
|
205
|
+
|
|
206
|
+
if (tok_last.Cmd == ECmdCode.cmVAL and tok_prev.Cmd == ECmdCode.cmVARMUL) or (
|
|
207
|
+
tok_last.Cmd == ECmdCode.cmVARMUL and tok_prev.Cmd == ECmdCode.cmVAL
|
|
208
|
+
):
|
|
209
|
+
tok_prev.Cmd = ECmdCode.cmVARMUL
|
|
210
|
+
tok_prev.ptr = tok_prev.ptr if tok_prev.ptr is not None else tok_last.ptr
|
|
211
|
+
if tok_last.Cmd == ECmdCode.cmVAL:
|
|
212
|
+
tok_prev.data = tok_prev.data * tok_last.data2
|
|
213
|
+
tok_prev.data2 = tok_prev.data2 * tok_last.data2
|
|
214
|
+
else:
|
|
215
|
+
tok_prev.data = tok_last.data * tok_prev.data2
|
|
216
|
+
tok_prev.data2 = tok_last.data2 * tok_prev.data2
|
|
217
|
+
self._rpn.pop()
|
|
218
|
+
return True
|
|
219
|
+
|
|
220
|
+
if (
|
|
221
|
+
tok_last.Cmd == ECmdCode.cmVAR
|
|
222
|
+
and tok_prev.Cmd == ECmdCode.cmVAR
|
|
223
|
+
and tok_last.ptr is tok_prev.ptr
|
|
224
|
+
):
|
|
225
|
+
tok_prev.Cmd = ECmdCode.cmVARPOW2
|
|
226
|
+
self._rpn.pop()
|
|
227
|
+
return True
|
|
228
|
+
|
|
229
|
+
return False
|
|
230
|
+
|
|
231
|
+
def _opt_div(self, tok_prev: SToken, tok_last: SToken) -> bool:
|
|
232
|
+
if (
|
|
233
|
+
tok_last.Cmd == ECmdCode.cmVAL
|
|
234
|
+
and tok_prev.Cmd == ECmdCode.cmVARMUL
|
|
235
|
+
and not is_array_like(tok_last.data2)
|
|
236
|
+
and tok_last.data2 != 0
|
|
237
|
+
):
|
|
238
|
+
tok_prev.data = tok_prev.data / tok_last.data2
|
|
239
|
+
tok_prev.data2 = tok_prev.data2 / tok_last.data2
|
|
240
|
+
self._rpn.pop()
|
|
241
|
+
return True
|
|
242
|
+
return False
|
|
243
|
+
|
|
244
|
+
def add_op(self, oprt: ECmdCode) -> None:
|
|
245
|
+
"""Emit a binary operator instruction, applying constant/linear folding if enabled."""
|
|
246
|
+
optimized = False
|
|
247
|
+
sz = len(self._rpn)
|
|
248
|
+
|
|
249
|
+
if self._enable_optimizer and sz >= 2:
|
|
250
|
+
tok_prev = self._rpn[sz - 2]
|
|
251
|
+
tok_last = self._rpn[sz - 1]
|
|
252
|
+
if tok_prev.Cmd == ECmdCode.cmVAL and tok_last.Cmd == ECmdCode.cmVAL:
|
|
253
|
+
optimized = self._constant_folding(oprt)
|
|
254
|
+
elif oprt == ECmdCode.cmPOW:
|
|
255
|
+
optimized = self._opt_pow(tok_prev, tok_last)
|
|
256
|
+
elif oprt in (ECmdCode.cmSUB, ECmdCode.cmADD):
|
|
257
|
+
optimized = self._opt_add_sub(oprt, tok_prev, tok_last)
|
|
258
|
+
elif oprt == ECmdCode.cmMUL:
|
|
259
|
+
optimized = self._opt_mul(tok_prev, tok_last)
|
|
260
|
+
elif oprt == ECmdCode.cmDIV:
|
|
261
|
+
optimized = self._opt_div(tok_prev, tok_last)
|
|
262
|
+
|
|
263
|
+
if not optimized:
|
|
264
|
+
self._stack_pos -= 1
|
|
265
|
+
self._rpn.append(SToken(Cmd=oprt))
|
|
266
|
+
|
|
267
|
+
def add_if_else(self, oprt: ECmdCode) -> None:
|
|
268
|
+
"""Emit a conditional jump marker (cmIF, cmELSE, or cmENDIF)."""
|
|
269
|
+
self._rpn.append(SToken(Cmd=oprt))
|
|
270
|
+
|
|
271
|
+
def add_assign_op(self, var_ref: VariableRef) -> None:
|
|
272
|
+
"""Emit a variable assignment instruction."""
|
|
273
|
+
self._stack_pos -= 1
|
|
274
|
+
self._rpn.append(SToken(Cmd=ECmdCode.cmASSIGN, ptr=var_ref))
|
|
275
|
+
|
|
276
|
+
def add_fun(self, cb: ParserCallback, argc: int, is_optimizable: bool) -> None:
|
|
277
|
+
"""Emit a function call instruction, folding constant arguments when possible."""
|
|
278
|
+
sz = len(self._rpn)
|
|
279
|
+
optimize = False
|
|
280
|
+
|
|
281
|
+
if is_optimizable and self._enable_optimizer and argc > 0:
|
|
282
|
+
if cb.func is MathImpl.UnaryPlus:
|
|
283
|
+
return
|
|
284
|
+
|
|
285
|
+
if sz >= argc and all(
|
|
286
|
+
self._rpn[sz - i - 1].Cmd == ECmdCode.cmVAL for i in range(argc)
|
|
287
|
+
):
|
|
288
|
+
optimize = True
|
|
289
|
+
|
|
290
|
+
if optimize:
|
|
291
|
+
args = [self._rpn[sz - argc + i].data2 for i in range(argc)]
|
|
292
|
+
val = cb.call_fun(*args)
|
|
293
|
+
del self._rpn[sz - argc :]
|
|
294
|
+
self._rpn.append(SToken(Cmd=ECmdCode.cmVAL, ptr=None, data=0.0, data2=val))
|
|
295
|
+
else:
|
|
296
|
+
self._rpn.append(SToken(Cmd=ECmdCode.cmFUNC, argc=argc, cb=cb))
|
|
297
|
+
|
|
298
|
+
self._stack_pos = self._stack_pos - abs(argc) + 1
|
|
299
|
+
self._max_stack_size = max(self._max_stack_size, self._stack_pos)
|
|
300
|
+
|
|
301
|
+
def add_bulk_fun(self, cb: ParserCallback, argc: int) -> None:
|
|
302
|
+
"""Emit a bulk-mode function call instruction."""
|
|
303
|
+
self._stack_pos = self._stack_pos - argc + 1
|
|
304
|
+
self._max_stack_size = max(self._max_stack_size, self._stack_pos)
|
|
305
|
+
self._rpn.append(SToken(Cmd=ECmdCode.cmFUNC_BULK, argc=argc, cb=cb))
|
|
306
|
+
|
|
307
|
+
def add_str_fun(self, cb: ParserCallback, argc: int, idx: int) -> None:
|
|
308
|
+
"""Emit a string-accepting function call instruction."""
|
|
309
|
+
self._stack_pos = self._stack_pos - argc + 1
|
|
310
|
+
self._max_stack_size = max(self._max_stack_size, self._stack_pos)
|
|
311
|
+
self._rpn.append(SToken(Cmd=ECmdCode.cmFUNC_STR, argc=argc, idx=idx, cb=cb))
|
|
312
|
+
|
|
313
|
+
def add_mult_str_fun(self, cb: ParserCallback, argc: int, idx: int) -> None:
|
|
314
|
+
"""Emit a variadic string-accepting function call instruction."""
|
|
315
|
+
self._stack_pos = self._stack_pos - argc + 1
|
|
316
|
+
self._max_stack_size = max(self._max_stack_size, self._stack_pos)
|
|
317
|
+
self._rpn.append(SToken(Cmd=ECmdCode.cmFUNC_STR, argc=-argc, idx=idx, cb=cb))
|
|
318
|
+
|
|
319
|
+
def finalize(self) -> None:
|
|
320
|
+
"""Append cmEND and compute jump offsets for all if-else-endif blocks."""
|
|
321
|
+
self._rpn.append(SToken(Cmd=ECmdCode.cmEND))
|
|
322
|
+
|
|
323
|
+
st_if: list[int] = []
|
|
324
|
+
st_else: list[int] = []
|
|
325
|
+
for i, tok in enumerate(self._rpn):
|
|
326
|
+
if tok.Cmd == ECmdCode.cmIF:
|
|
327
|
+
st_if.append(i)
|
|
328
|
+
elif tok.Cmd == ECmdCode.cmELSE:
|
|
329
|
+
st_else.append(i)
|
|
330
|
+
if not st_if:
|
|
331
|
+
raise ParserError(EErrorCodes.ecINTERNAL_ERROR)
|
|
332
|
+
idx = st_if.pop()
|
|
333
|
+
self._rpn[idx].offset = i - idx
|
|
334
|
+
elif tok.Cmd == ECmdCode.cmENDIF:
|
|
335
|
+
if not st_else:
|
|
336
|
+
raise ParserError(EErrorCodes.ecINTERNAL_ERROR)
|
|
337
|
+
idx = st_else.pop()
|
|
338
|
+
self._rpn[idx].offset = i - idx
|
|
339
|
+
|
|
340
|
+
def store_environment(self, expr: str, string_buf: list[str]) -> None:
|
|
341
|
+
"""Save the compiled expression string and string literal buffer."""
|
|
342
|
+
self._expr = expr
|
|
343
|
+
self._string_buffer = list(string_buf)
|
|
344
|
+
|
|
345
|
+
def restore_environment(self) -> tuple[str, list[str]]:
|
|
346
|
+
"""Return the stored expression string and string literal buffer."""
|
|
347
|
+
return self._expr, list(self._string_buffer)
|
|
348
|
+
|
|
349
|
+
def get_base(self) -> list[SToken]:
|
|
350
|
+
"""Return the underlying list of RPN bytecode tokens."""
|
|
351
|
+
return self._rpn
|
|
352
|
+
|
|
353
|
+
def get_max_stack_size(self) -> int:
|
|
354
|
+
"""Return the maximum evaluation stack depth required."""
|
|
355
|
+
return self._max_stack_size + 1
|
|
356
|
+
|
|
357
|
+
def get_size(self) -> int:
|
|
358
|
+
"""Return the number of tokens in the RPN stream."""
|
|
359
|
+
return len(self._rpn)
|
|
360
|
+
|
|
361
|
+
def clear(self) -> None:
|
|
362
|
+
"""Reset the bytecode stream and stack counters."""
|
|
363
|
+
self._rpn.clear()
|
|
364
|
+
self._stack_pos = 0
|
|
365
|
+
self._max_stack_size = 0
|
|
366
|
+
|
|
367
|
+
# C++ style aliases
|
|
368
|
+
EnableOptimizer = enable_optimizer
|
|
369
|
+
AddVar = add_var
|
|
370
|
+
AddVal = add_val
|
|
371
|
+
AddOp = add_op
|
|
372
|
+
AddIfElse = add_if_else
|
|
373
|
+
AddAssignOp = add_assign_op
|
|
374
|
+
AddFun = add_fun
|
|
375
|
+
AddBulkFun = add_bulk_fun
|
|
376
|
+
AddStrFun = add_str_fun
|
|
377
|
+
AddMultStrFun = add_mult_str_fun
|
|
378
|
+
Finalize = finalize
|
|
379
|
+
StoreEnvironment = store_environment
|
|
380
|
+
RestoreEnvironment = restore_environment
|
|
381
|
+
GetBase = get_base
|
|
382
|
+
GetMaxStackSize = get_max_stack_size
|
|
383
|
+
GetSize = get_size
|
pymu_eval/definitions.py
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Standard definitions, enums, and constants for pymu-eval.
|
|
3
|
+
Mirrors muParserDef.h and muParserTokenReader.h from the C++ muParser library.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
# pylint: disable=invalid-name
|
|
7
|
+
|
|
8
|
+
from enum import IntEnum, IntFlag
|
|
9
|
+
|
|
10
|
+
MaxLenExpression: int = 20000
|
|
11
|
+
MaxLenIdentifier: int = 100
|
|
12
|
+
MaxNestingDepth: int = 1000
|
|
13
|
+
|
|
14
|
+
ParserVersion: str = "2.3.6 (Development)"
|
|
15
|
+
ParserVersionDate: str = "20261110"
|
|
16
|
+
|
|
17
|
+
DEFAULT_OPRT: tuple[str, ...] = (
|
|
18
|
+
"<=",
|
|
19
|
+
">=",
|
|
20
|
+
"!=",
|
|
21
|
+
"==",
|
|
22
|
+
"<",
|
|
23
|
+
">",
|
|
24
|
+
"+",
|
|
25
|
+
"-",
|
|
26
|
+
"*",
|
|
27
|
+
"/",
|
|
28
|
+
"^",
|
|
29
|
+
"&&",
|
|
30
|
+
"||",
|
|
31
|
+
"=",
|
|
32
|
+
"(",
|
|
33
|
+
")",
|
|
34
|
+
"?",
|
|
35
|
+
":",
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
BINARY_OPRT_CODES: frozenset[int] = frozenset(range(14)) # cmLE (0) .. cmASSIGN (13)
|
|
39
|
+
CMP_OPRT_CODES: frozenset[int] = frozenset(range(6)) # cmLE (0) .. cmGT (5)
|
|
40
|
+
ARITH_OPRT_CODES: frozenset[int] = frozenset(range(6, 11)) # cmADD (6) .. cmPOW (10)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ECmdCode(IntEnum):
|
|
44
|
+
"""Bytecode and token command codes (matches mu::ECmdCode)."""
|
|
45
|
+
|
|
46
|
+
cmLE = 0 # Operator item: less or equal
|
|
47
|
+
cmGE = 1 # Operator item: greater or equal
|
|
48
|
+
cmNEQ = 2 # Operator item: not equal
|
|
49
|
+
cmEQ = 3 # Operator item: equals
|
|
50
|
+
cmLT = 4 # Operator item: less than
|
|
51
|
+
cmGT = 5 # Operator item: greater than
|
|
52
|
+
cmADD = 6 # Operator item: add
|
|
53
|
+
cmSUB = 7 # Operator item: subtract
|
|
54
|
+
cmMUL = 8 # Operator item: multiply
|
|
55
|
+
cmDIV = 9 # Operator item: division
|
|
56
|
+
cmPOW = 10 # Operator item: y to the power of ...
|
|
57
|
+
cmLAND = 11 # Operator item: logical and
|
|
58
|
+
cmLOR = 12 # Operator item: logical or
|
|
59
|
+
cmASSIGN = 13 # Operator item: assignment operator
|
|
60
|
+
cmBO = 14 # Operator item: opening bracket
|
|
61
|
+
cmBC = 15 # Operator item: closing bracket
|
|
62
|
+
cmIF = 16 # For use in the ternary if-then-else operator
|
|
63
|
+
cmELSE = 17 # For use in the ternary if-then-else operator
|
|
64
|
+
cmENDIF = 18 # For use in the ternary if-then-else operator
|
|
65
|
+
cmARG_SEP = 19 # Function argument separator
|
|
66
|
+
cmVAR = 20 # Variable item
|
|
67
|
+
cmVAL = 21 # Value item
|
|
68
|
+
|
|
69
|
+
# For optimization purposes
|
|
70
|
+
cmVARPOW2 = 22
|
|
71
|
+
cmVARPOW3 = 23
|
|
72
|
+
cmVARPOW4 = 24
|
|
73
|
+
cmVARMUL = 25
|
|
74
|
+
|
|
75
|
+
# Operators and functions
|
|
76
|
+
cmFUNC = 26 # Code for a generic function item
|
|
77
|
+
cmFUNC_STR = 27 # Code for a function with a string parameter
|
|
78
|
+
cmFUNC_BULK = 28 # Special callbacks for Bulk mode
|
|
79
|
+
cmSTRING = 29 # Code for a string token
|
|
80
|
+
cmOPRT_BIN = 30 # User defined binary operator
|
|
81
|
+
cmOPRT_POSTFIX = 31 # Code for postfix operators
|
|
82
|
+
cmOPRT_INFIX = 32 # Code for infix operators
|
|
83
|
+
cmEND = 33 # End of formula
|
|
84
|
+
cmUNKNOWN = 34 # Uninitialized item
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class ETypeCode(IntEnum):
|
|
88
|
+
"""Types internally used by the parser (matches mu::ETypeCode)."""
|
|
89
|
+
|
|
90
|
+
tpSTR = 0 # String type
|
|
91
|
+
tpDBL = 1 # Floating point / numeric / array variables
|
|
92
|
+
tpVOID = 2 # Undefined type
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class EParserVersionInfo(IntEnum):
|
|
96
|
+
"""Version info formatting flags."""
|
|
97
|
+
|
|
98
|
+
pviBRIEF = 0
|
|
99
|
+
pviFULL = 1
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class EOprtAssociativity(IntEnum):
|
|
103
|
+
"""Parser operator associativity values (matches mu::EOprtAssociativity)."""
|
|
104
|
+
|
|
105
|
+
oaLEFT = 0
|
|
106
|
+
oaRIGHT = 1
|
|
107
|
+
oaNONE = 2
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class EOprtPrecedence(IntEnum):
|
|
111
|
+
"""Parser operator precedence values (matches mu::EOprtPrecedence)."""
|
|
112
|
+
|
|
113
|
+
prLOR = 1 # logic or
|
|
114
|
+
prLAND = 2 # logic and
|
|
115
|
+
prBOR = 3 # bitwise or
|
|
116
|
+
prBAND = 4 # bitwise and
|
|
117
|
+
prCMP = 5 # comparison operators
|
|
118
|
+
prADD_SUB = 6 # addition / subtraction
|
|
119
|
+
prMUL_DIV = 7 # multiplication / division
|
|
120
|
+
prPOW = 8 # power operator priority (highest)
|
|
121
|
+
|
|
122
|
+
# infix / postfix operators
|
|
123
|
+
prINFIX = 7 # Signs have a higher priority than ADD_SUB, but lower than power operator
|
|
124
|
+
prPOSTFIX = 7 # Postfix operator priority
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class ESynCodes(IntFlag):
|
|
128
|
+
"""Syntax flags controlling allowed next tokens during lexing."""
|
|
129
|
+
|
|
130
|
+
noNONE = 0
|
|
131
|
+
noBO = 1 << 0 # to avoid i.e. "cos(7)("
|
|
132
|
+
noBC = 1 << 1 # to avoid i.e. "sin)" or "()"
|
|
133
|
+
noVAL = 1 << 2 # to avoid i.e. "tan 2" or "sin(8)3.14"
|
|
134
|
+
noVAR = 1 << 3 # to avoid i.e. "sin a" or "sin(8)a"
|
|
135
|
+
noARG_SEP = 1 << 4 # to avoid i.e. ",," or "+," ...
|
|
136
|
+
noFUN = 1 << 5 # to avoid i.e. "sqrt cos" or "(1)sin"
|
|
137
|
+
noOPT = 1 << 6 # to avoid i.e. "(+)"
|
|
138
|
+
noPOSTOP = 1 << 7 # to avoid i.e. "(5!!)" "sin!"
|
|
139
|
+
noINFIXOP = 1 << 8 # to avoid i.e. "++4" "!!4"
|
|
140
|
+
noEND = 1 << 9 # to avoid unexpected end of formula
|
|
141
|
+
noSTR = 1 << 10 # to block numeric arguments on string functions
|
|
142
|
+
noASSIGN = 1 << 11 # to block assignment to constant i.e. "4=7"
|
|
143
|
+
noIF = 1 << 12
|
|
144
|
+
noELSE = 1 << 13
|
|
145
|
+
sfSTART_OF_LINE = noOPT | noBC | noPOSTOP | noASSIGN | noIF | noELSE | noARG_SEP
|
|
146
|
+
noANY = (1 << 14) - 1
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class EErrorCodes(IntEnum):
|
|
150
|
+
"""Error codes (matches mu::EErrorCodes)."""
|
|
151
|
+
|
|
152
|
+
# Formula syntax errors
|
|
153
|
+
ecUNEXPECTED_OPERATOR = 0
|
|
154
|
+
ecUNASSIGNABLE_TOKEN = 1
|
|
155
|
+
ecUNEXPECTED_EOF = 2
|
|
156
|
+
ecUNEXPECTED_ARG_SEP = 3
|
|
157
|
+
ecUNEXPECTED_ARG = 4
|
|
158
|
+
ecUNEXPECTED_VAL = 5
|
|
159
|
+
ecUNEXPECTED_VAR = 6
|
|
160
|
+
ecUNEXPECTED_PARENS = 7
|
|
161
|
+
ecUNEXPECTED_STR = 8
|
|
162
|
+
ecSTRING_EXPECTED = 9
|
|
163
|
+
ecVAL_EXPECTED = 10
|
|
164
|
+
ecMISSING_PARENS = 11
|
|
165
|
+
ecUNEXPECTED_FUN = 12
|
|
166
|
+
ecUNTERMINATED_STRING = 13
|
|
167
|
+
ecTOO_MANY_PARAMS = 14
|
|
168
|
+
ecTOO_FEW_PARAMS = 15
|
|
169
|
+
ecOPRT_TYPE_CONFLICT = 16
|
|
170
|
+
ecSTR_RESULT = 17
|
|
171
|
+
|
|
172
|
+
# Invalid Parser input Parameters
|
|
173
|
+
ecINVALID_NAME = 18
|
|
174
|
+
ecINVALID_BINOP_IDENT = 19
|
|
175
|
+
ecINVALID_INFIX_IDENT = 20
|
|
176
|
+
ecINVALID_POSTFIX_IDENT = 21
|
|
177
|
+
|
|
178
|
+
ecBUILTIN_OVERLOAD = 22
|
|
179
|
+
ecINVALID_FUN_PTR = 23
|
|
180
|
+
ecINVALID_VAR_PTR = 24
|
|
181
|
+
ecEMPTY_EXPRESSION = 25
|
|
182
|
+
ecNAME_CONFLICT = 26
|
|
183
|
+
ecOPT_PRI = 27
|
|
184
|
+
|
|
185
|
+
ecDOMAIN_ERROR = 28
|
|
186
|
+
ecDIV_BY_ZERO = 29
|
|
187
|
+
ecGENERIC = 30
|
|
188
|
+
ecLOCALE = 31
|
|
189
|
+
|
|
190
|
+
ecUNEXPECTED_CONDITIONAL = 32
|
|
191
|
+
ecMISSING_ELSE_CLAUSE = 33
|
|
192
|
+
ecMISPLACED_COLON = 34
|
|
193
|
+
|
|
194
|
+
ecUNREASONABLE_NUMBER_OF_COMPUTATIONS = 35
|
|
195
|
+
ecIDENTIFIER_TOO_LONG = 36
|
|
196
|
+
ecEXPRESSION_TOO_LONG = 37
|
|
197
|
+
ecINVALID_CHARACTERS_FOUND = 38
|
|
198
|
+
|
|
199
|
+
# Internal errors
|
|
200
|
+
ecINTERNAL_ERROR = 39
|
|
201
|
+
ecBYTECODE_IMPORT_EXPORT_DISABLED = 40
|
|
202
|
+
ecUNARY_PLUS_IN_FRONT_OF_FUNCTION = 41
|
|
203
|
+
ecNESTING_LIMIT = 42
|
|
204
|
+
|
|
205
|
+
ecCOUNT = 43
|
|
206
|
+
ecUNDEFINED = -1
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
# Convenient aliases
|
|
210
|
+
CmdCode = ECmdCode
|
|
211
|
+
TypeCode = ETypeCode
|
|
212
|
+
OprtAssociativity = EOprtAssociativity
|
|
213
|
+
OprtPrecedence = EOprtPrecedence
|
|
214
|
+
ErrorCode = EErrorCodes
|
|
215
|
+
|
|
216
|
+
PUBLIC_ENUMS = (
|
|
217
|
+
ECmdCode,
|
|
218
|
+
ETypeCode,
|
|
219
|
+
EParserVersionInfo,
|
|
220
|
+
EOprtAssociativity,
|
|
221
|
+
EOprtPrecedence,
|
|
222
|
+
EErrorCodes,
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
# Export individual enum members at module level for C++ style access
|
|
226
|
+
for _enum_cls in PUBLIC_ENUMS:
|
|
227
|
+
globals().update(_enum_cls.__members__)
|