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 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
@@ -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__)