math-engine 0.4.0__tar.gz → 0.5.0__tar.gz
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.
- {math_engine-0.4.0 → math_engine-0.5.0}/PKG-INFO +34 -5
- {math_engine-0.4.0 → math_engine-0.5.0}/README.md +33 -4
- math_engine-0.5.0/math_engine/AST_Node_Types.py +184 -0
- {math_engine-0.4.0 → math_engine-0.5.0}/math_engine/__init__.py +8 -5
- {math_engine-0.4.0 → math_engine-0.5.0}/math_engine/calculator.py +258 -438
- {math_engine-0.4.0 → math_engine-0.5.0}/math_engine/config.json +1 -1
- {math_engine-0.4.0 → math_engine-0.5.0}/math_engine/error.py +2 -0
- math_engine-0.5.0/math_engine/non_decimal_utility.py +240 -0
- math_engine-0.5.0/math_engine/utility.py +97 -0
- {math_engine-0.4.0 → math_engine-0.5.0}/math_engine.egg-info/PKG-INFO +34 -5
- {math_engine-0.4.0 → math_engine-0.5.0}/math_engine.egg-info/SOURCES.txt +3 -0
- {math_engine-0.4.0 → math_engine-0.5.0}/pyproject.toml +1 -1
- {math_engine-0.4.0 → math_engine-0.5.0}/LICENSE +0 -0
- {math_engine-0.4.0 → math_engine-0.5.0}/math_engine/ScientificEngine.py +0 -0
- {math_engine-0.4.0 → math_engine-0.5.0}/math_engine/cli.py +0 -0
- {math_engine-0.4.0 → math_engine-0.5.0}/math_engine/config_manager.py +0 -0
- {math_engine-0.4.0 → math_engine-0.5.0}/math_engine.egg-info/dependency_links.txt +0 -0
- {math_engine-0.4.0 → math_engine-0.5.0}/math_engine.egg-info/entry_points.txt +0 -0
- {math_engine-0.4.0 → math_engine-0.5.0}/math_engine.egg-info/top_level.txt +0 -0
- {math_engine-0.4.0 → math_engine-0.5.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: math-engine
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: A fast and secure mathematical expression evaluator.
|
|
5
5
|
Author-email: Jan Teske <jan.teske.06@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/JanTeske06/math_engine
|
|
@@ -14,7 +14,7 @@ Description-Content-Type: text/markdown
|
|
|
14
14
|
License-File: LICENSE
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
# Math Engine 0.
|
|
17
|
+
# Math Engine 0.5.0
|
|
18
18
|
|
|
19
19
|
[](https://pypi.org/project/math-engine/)
|
|
20
20
|
[](https://opensource.org/licenses/MIT)
|
|
@@ -34,7 +34,7 @@ It provides a complete pipeline:
|
|
|
34
34
|
* Scientific functions
|
|
35
35
|
* Strict error codes for reliable debugging and automated testing
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
|
|
38
38
|
|
|
39
39
|
This library is ideal for:
|
|
40
40
|
|
|
@@ -263,7 +263,7 @@ Non-decimal parsing respects the setting `allow_non_decimal`. If it is set to `F
|
|
|
263
263
|
|
|
264
264
|
-----
|
|
265
265
|
|
|
266
|
-
# Bitwise Operations & Developer Mode (v0.
|
|
266
|
+
# Bitwise Operations & Developer Mode (v0.5.0)
|
|
267
267
|
|
|
268
268
|
Math Engine can act as a **programmer's calculator**. It supports standard operator precedence and bitwise logic.
|
|
269
269
|
|
|
@@ -326,9 +326,38 @@ math_engine.load_preset(settings)
|
|
|
326
326
|
math_engine.evaluate("FF + 3")
|
|
327
327
|
# Decimal('258')
|
|
328
328
|
```
|
|
329
|
-
|
|
330
329
|
Input validation ensures safety and prevents mixing incompatible formats in strict modes.
|
|
331
330
|
|
|
331
|
+
---
|
|
332
|
+
## Bitwise & Low-Level Operations
|
|
333
|
+
|
|
334
|
+
Math Engine now includes a rich collection of low-level bit manipulation functions commonly used in systems programming, embedded development, cryptography, and hardware-oriented tools.
|
|
335
|
+
|
|
336
|
+
**Bitwise functions:**
|
|
337
|
+
- `bitand(x, y)` — bitwise AND
|
|
338
|
+
- `bitor(x, y)` — bitwise OR
|
|
339
|
+
- `bitxor(x, y)` — bitwise XOR
|
|
340
|
+
- `bitnot(x)` — bitwise NOT
|
|
341
|
+
|
|
342
|
+
**Bit manipulation utilities:**
|
|
343
|
+
- `setbit(x, n)` — sets bit *n*
|
|
344
|
+
- `clrbit(x, n)` — clears bit *n*
|
|
345
|
+
- `togbit(x, n)` — toggles bit *n*
|
|
346
|
+
- `testbit(x, n)` — returns 1 if bit *n* is set, else 0
|
|
347
|
+
|
|
348
|
+
**Shift operations:**
|
|
349
|
+
- `shl(x, n)` — logical left shift
|
|
350
|
+
- `shr(x, n)` — logical right shift
|
|
351
|
+
|
|
352
|
+
All bitwise functions:
|
|
353
|
+
- respect `word_size` and `signed_mode`
|
|
354
|
+
- support overflow/wrap-around behavior
|
|
355
|
+
- fully support binary, hex, decimal, and octal inputs
|
|
356
|
+
- participate in the AST just like standard operators
|
|
357
|
+
- support underscores in non-decimal literals (`0b1111_0000`)
|
|
358
|
+
|
|
359
|
+
This makes Math Engine behave like a full-featured programmer’s calculator with CPU-like precision control.
|
|
360
|
+
|
|
332
361
|
-----
|
|
333
362
|
|
|
334
363
|
# Settings System
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
# Math Engine 0.
|
|
2
|
+
# Math Engine 0.5.0
|
|
3
3
|
|
|
4
4
|
[](https://pypi.org/project/math-engine/)
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
@@ -19,7 +19,7 @@ It provides a complete pipeline:
|
|
|
19
19
|
* Scientific functions
|
|
20
20
|
* Strict error codes for reliable debugging and automated testing
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
|
|
23
23
|
|
|
24
24
|
This library is ideal for:
|
|
25
25
|
|
|
@@ -248,7 +248,7 @@ Non-decimal parsing respects the setting `allow_non_decimal`. If it is set to `F
|
|
|
248
248
|
|
|
249
249
|
-----
|
|
250
250
|
|
|
251
|
-
# Bitwise Operations & Developer Mode (v0.
|
|
251
|
+
# Bitwise Operations & Developer Mode (v0.5.0)
|
|
252
252
|
|
|
253
253
|
Math Engine can act as a **programmer's calculator**. It supports standard operator precedence and bitwise logic.
|
|
254
254
|
|
|
@@ -311,9 +311,38 @@ math_engine.load_preset(settings)
|
|
|
311
311
|
math_engine.evaluate("FF + 3")
|
|
312
312
|
# Decimal('258')
|
|
313
313
|
```
|
|
314
|
-
|
|
315
314
|
Input validation ensures safety and prevents mixing incompatible formats in strict modes.
|
|
316
315
|
|
|
316
|
+
---
|
|
317
|
+
## Bitwise & Low-Level Operations
|
|
318
|
+
|
|
319
|
+
Math Engine now includes a rich collection of low-level bit manipulation functions commonly used in systems programming, embedded development, cryptography, and hardware-oriented tools.
|
|
320
|
+
|
|
321
|
+
**Bitwise functions:**
|
|
322
|
+
- `bitand(x, y)` — bitwise AND
|
|
323
|
+
- `bitor(x, y)` — bitwise OR
|
|
324
|
+
- `bitxor(x, y)` — bitwise XOR
|
|
325
|
+
- `bitnot(x)` — bitwise NOT
|
|
326
|
+
|
|
327
|
+
**Bit manipulation utilities:**
|
|
328
|
+
- `setbit(x, n)` — sets bit *n*
|
|
329
|
+
- `clrbit(x, n)` — clears bit *n*
|
|
330
|
+
- `togbit(x, n)` — toggles bit *n*
|
|
331
|
+
- `testbit(x, n)` — returns 1 if bit *n* is set, else 0
|
|
332
|
+
|
|
333
|
+
**Shift operations:**
|
|
334
|
+
- `shl(x, n)` — logical left shift
|
|
335
|
+
- `shr(x, n)` — logical right shift
|
|
336
|
+
|
|
337
|
+
All bitwise functions:
|
|
338
|
+
- respect `word_size` and `signed_mode`
|
|
339
|
+
- support overflow/wrap-around behavior
|
|
340
|
+
- fully support binary, hex, decimal, and octal inputs
|
|
341
|
+
- participate in the AST just like standard operators
|
|
342
|
+
- support underscores in non-decimal literals (`0b1111_0000`)
|
|
343
|
+
|
|
344
|
+
This makes Math Engine behave like a full-featured programmer’s calculator with CPU-like precision control.
|
|
345
|
+
|
|
317
346
|
-----
|
|
318
347
|
|
|
319
348
|
# Settings System
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
from decimal import Decimal, getcontext, Overflow
|
|
2
|
+
from . import error as E
|
|
3
|
+
|
|
4
|
+
# -----------------------------
|
|
5
|
+
# AST node types
|
|
6
|
+
# -----------------------------
|
|
7
|
+
|
|
8
|
+
class Number:
|
|
9
|
+
"""AST node for numeric literal backed by Decimal."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, value):
|
|
12
|
+
# Always normalize input to Decimal via string to avoid float artifacts
|
|
13
|
+
if not isinstance(value, Decimal):
|
|
14
|
+
value = str(value)
|
|
15
|
+
self.value = Decimal(value)
|
|
16
|
+
|
|
17
|
+
def evaluate(self):
|
|
18
|
+
"""Return Decimal value for this literal."""
|
|
19
|
+
return self.value
|
|
20
|
+
|
|
21
|
+
def collect_term(self, var_name):
|
|
22
|
+
"""Return (factor_of_var, constant) for linear collection."""
|
|
23
|
+
return (0, self.value)
|
|
24
|
+
|
|
25
|
+
def __repr__(self):
|
|
26
|
+
# Helpful for debugging/printing the AST
|
|
27
|
+
try:
|
|
28
|
+
display_value = self.value.to_normal_string()
|
|
29
|
+
except AttributeError:
|
|
30
|
+
# Fallback for older Decimal versions
|
|
31
|
+
display_value = str(self.value)
|
|
32
|
+
return f"Number({display_value})"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Variable:
|
|
36
|
+
"""AST node representing a single symbolic variable (e.g. 'var0')."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, name):
|
|
39
|
+
self.name = name
|
|
40
|
+
|
|
41
|
+
def evaluate(self):
|
|
42
|
+
"""Variables cannot be directly evaluated without solving."""
|
|
43
|
+
raise E.SolverError(f"Non linear problem.", code="3005")
|
|
44
|
+
|
|
45
|
+
def collect_term(self, var_name):
|
|
46
|
+
"""Return (1, 0) if this variable matches var_name; else error."""
|
|
47
|
+
if self.name == var_name:
|
|
48
|
+
return (1, 0)
|
|
49
|
+
else:
|
|
50
|
+
# Only one variable supported in the linear solver
|
|
51
|
+
raise E.SolverError(f"Multiple variables found: {self.name}", code="3002")
|
|
52
|
+
return (0, 0)
|
|
53
|
+
|
|
54
|
+
def __repr__(self):
|
|
55
|
+
return f"Variable('{self.name}')"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class BinOp:
|
|
59
|
+
"""AST node for a binary operation: left <operator> right."""
|
|
60
|
+
|
|
61
|
+
def __init__(self, left, operator, right):
|
|
62
|
+
self.left = left
|
|
63
|
+
self.operator = operator
|
|
64
|
+
self.right = right
|
|
65
|
+
|
|
66
|
+
def evaluate(self):
|
|
67
|
+
"""Evaluate numeric subtree and apply the binary operator."""
|
|
68
|
+
left_value = self.left.evaluate()
|
|
69
|
+
right_value = self.right.evaluate()
|
|
70
|
+
|
|
71
|
+
if self.operator == '+':
|
|
72
|
+
return left_value + right_value
|
|
73
|
+
|
|
74
|
+
elif self.operator == '-':
|
|
75
|
+
return left_value - right_value
|
|
76
|
+
|
|
77
|
+
elif self.operator == '&':
|
|
78
|
+
if left_value % 1 != 0 or right_value % 1 != 0:
|
|
79
|
+
raise E.CalculationError("Bitwise AND requires integers.", code="3042")
|
|
80
|
+
return Decimal(int(left_value) & int(right_value))
|
|
81
|
+
|
|
82
|
+
elif self.operator == '|':
|
|
83
|
+
if left_value % 1 != 0 or right_value % 1 != 0:
|
|
84
|
+
raise E.CalculationError("Bitwise OR requires integers.", code="3042")
|
|
85
|
+
return Decimal(int(left_value) | int(right_value))
|
|
86
|
+
|
|
87
|
+
elif self.operator == '^':
|
|
88
|
+
if left_value % 1 != 0 or right_value % 1 != 0:
|
|
89
|
+
raise E.CalculationError("XOR requires integers.", code="3042")
|
|
90
|
+
return Decimal(int(left_value) ^ int(right_value))
|
|
91
|
+
|
|
92
|
+
elif self.operator == '<<':
|
|
93
|
+
if left_value % 1 != 0 or right_value % 1 != 0:
|
|
94
|
+
raise E.CalculationError("Bitshift requires integers.", code="3041")
|
|
95
|
+
return Decimal(int(left_value) << int(right_value))
|
|
96
|
+
|
|
97
|
+
elif self.operator == '>>':
|
|
98
|
+
if left_value % 1 != 0 or right_value % 1 != 0:
|
|
99
|
+
raise E.CalculationError("Bitshift requires integers.", code="3041")
|
|
100
|
+
return Decimal(int(left_value) >> int(right_value))
|
|
101
|
+
|
|
102
|
+
elif self.operator == '*':
|
|
103
|
+
return left_value * right_value
|
|
104
|
+
|
|
105
|
+
elif self.operator == '**':
|
|
106
|
+
return left_value ** right_value
|
|
107
|
+
|
|
108
|
+
elif self.operator == '/':
|
|
109
|
+
if right_value == 0:
|
|
110
|
+
raise E.CalculationError("Division by zero", code="3003")
|
|
111
|
+
return left_value / right_value
|
|
112
|
+
|
|
113
|
+
elif self.operator == '=':
|
|
114
|
+
# Equality is evaluated to a boolean (used for "= True/False" responses)
|
|
115
|
+
return left_value == right_value
|
|
116
|
+
else:
|
|
117
|
+
raise E.CalculationError(f"Unknown operator: {self.operator}", code="3004")
|
|
118
|
+
|
|
119
|
+
def collect_term(self, var_name):
|
|
120
|
+
"""Collect linear terms on this subtree into (factor_of_var, constant).
|
|
121
|
+
|
|
122
|
+
Only linear combinations are allowed; non-linear forms raise Solver/Syntax errors.
|
|
123
|
+
"""
|
|
124
|
+
(left_factor, left_constant) = self.left.collect_term(var_name)
|
|
125
|
+
(right_factor, right_constant) = self.right.collect_term(var_name)
|
|
126
|
+
|
|
127
|
+
if self.operator == '+':
|
|
128
|
+
result_factor = left_factor + right_factor
|
|
129
|
+
result_constant = left_constant + right_constant
|
|
130
|
+
return (result_factor, result_constant)
|
|
131
|
+
|
|
132
|
+
elif self.operator == '-':
|
|
133
|
+
result_factor = left_factor - right_factor
|
|
134
|
+
result_constant = left_constant - right_constant
|
|
135
|
+
return (result_factor, result_constant)
|
|
136
|
+
|
|
137
|
+
elif self.operator == '*':
|
|
138
|
+
# Only constant * (A*x + B) is allowed. (A*x + B)*(C*x + D) would be non-linear.
|
|
139
|
+
if left_factor != 0 and right_factor != 0:
|
|
140
|
+
raise E.SyntaxError("x^x Error.", code="3005")
|
|
141
|
+
|
|
142
|
+
elif left_factor == 0:
|
|
143
|
+
# B * (C*x + D) = (B*C)*x + (B*D)
|
|
144
|
+
result_factor = left_constant * right_factor
|
|
145
|
+
result_constant = left_constant * right_constant
|
|
146
|
+
return (result_factor, result_constant)
|
|
147
|
+
|
|
148
|
+
elif right_factor == 0:
|
|
149
|
+
# (A*x + B) * D = (A*D)*x + (B*D)
|
|
150
|
+
result_factor = right_constant * left_factor
|
|
151
|
+
result_constant = right_constant * left_constant
|
|
152
|
+
return (result_factor, result_constant)
|
|
153
|
+
|
|
154
|
+
elif left_factor == 0 and right_factor == 0:
|
|
155
|
+
# Pure constant multiplication
|
|
156
|
+
result_factor = 0
|
|
157
|
+
result_constant = right_constant * left_constant
|
|
158
|
+
return (result_factor, result_constant)
|
|
159
|
+
|
|
160
|
+
elif self.operator == '/':
|
|
161
|
+
# (A*x + B) / D is allowed; division by (C*x + D) is non-linear
|
|
162
|
+
if right_factor != 0:
|
|
163
|
+
raise E.SolverError("Non-linear equation. (Division by x)", code="3006")
|
|
164
|
+
elif right_constant == 0:
|
|
165
|
+
raise E.SolverError("Solver: Division by zero", code="3003")
|
|
166
|
+
else:
|
|
167
|
+
# (A*x + B) / D = (A/D)*x + (B/D)
|
|
168
|
+
result_factor = left_factor / right_constant
|
|
169
|
+
result_constant = left_constant / right_constant
|
|
170
|
+
return (result_factor, result_constant)
|
|
171
|
+
|
|
172
|
+
elif self.operator == '**':
|
|
173
|
+
# Powers generate non-linear terms (e.g., x^2)
|
|
174
|
+
raise E.SolverError("Powers are not supported by the linear solver.", code="3007")
|
|
175
|
+
|
|
176
|
+
elif self.operator == '=':
|
|
177
|
+
# '=' only belongs at the root for solving; not inside collection
|
|
178
|
+
raise E.SolverError("Should not happen: '=' inside collect_terms", code="3720")
|
|
179
|
+
|
|
180
|
+
else:
|
|
181
|
+
raise E.CalculationError(f"Unknown operator: {self.operator}", code="3004")
|
|
182
|
+
|
|
183
|
+
def __repr__(self):
|
|
184
|
+
return f"BinOp({self.operator!r}, left={self.left}, right={self.right})"
|
|
@@ -8,7 +8,7 @@ from . import error as E
|
|
|
8
8
|
from typing import Any, Mapping, Optional
|
|
9
9
|
from typing import Union
|
|
10
10
|
from typing import Any, Mapping
|
|
11
|
-
__version__ = "0.
|
|
11
|
+
__version__ = "0.5.0"
|
|
12
12
|
memory = {}
|
|
13
13
|
|
|
14
14
|
def set_memory(key_value: str, value:str):
|
|
@@ -119,7 +119,10 @@ def validate(expr: str,
|
|
|
119
119
|
def reset_settings():
|
|
120
120
|
config_manager.reset_settings()
|
|
121
121
|
#
|
|
122
|
-
if __name__ == '__main__':
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
122
|
+
# if __name__ == '__main__':
|
|
123
|
+
# #problem = ("x+1", x=5)
|
|
124
|
+
# print(math_engine.evaluate("bitxor(shr(shl(bitnot(7), 2), 4), 3) + 100"))
|
|
125
|
+
# print(evaluate("int:(bitand(0b1101,0b1011)+ bitor(0b0011,0b0101)+ bitxor(0xF0,0b1010)+ shl(3,4)+ shr(0b100000,3)+ setbit(0b0001,2)+ clrbit(0b1111,1)+ togbit(0b1010,1))"))
|
|
126
|
+
# print(evaluate("bool:testbit(0b1010, 3)"))
|
|
127
|
+
# #print(math_engine.evaluate("bitor(bitand(0b101110110, bitnot(4096)),160) * 3"))
|
|
128
|
+
# config_manager.reset_settings()
|