math-engine 0.5.0__tar.gz → 0.6.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.5.0 → math_engine-0.6.0}/PKG-INFO +56 -42
- {math_engine-0.5.0 → math_engine-0.6.0}/README.md +55 -41
- {math_engine-0.5.0 → math_engine-0.6.0}/math_engine/AST_Node_Types.py +36 -59
- math_engine-0.6.0/math_engine/__init__.py +188 -0
- {math_engine-0.5.0 → math_engine-0.6.0}/math_engine/calculator.py +337 -324
- {math_engine-0.5.0 → math_engine-0.6.0}/math_engine/cli.py +4 -3
- {math_engine-0.5.0 → math_engine-0.6.0}/math_engine/config.json +1 -0
- {math_engine-0.5.0 → math_engine-0.6.0}/math_engine/config_manager.py +22 -1
- {math_engine-0.5.0 → math_engine-0.6.0}/math_engine/error.py +5 -2
- {math_engine-0.5.0 → math_engine-0.6.0}/math_engine/non_decimal_utility.py +1 -1
- {math_engine-0.5.0 → math_engine-0.6.0}/math_engine.egg-info/PKG-INFO +56 -42
- {math_engine-0.5.0 → math_engine-0.6.0}/math_engine.egg-info/entry_points.txt +1 -0
- {math_engine-0.5.0 → math_engine-0.6.0}/pyproject.toml +2 -1
- math_engine-0.5.0/math_engine/__init__.py +0 -128
- {math_engine-0.5.0 → math_engine-0.6.0}/LICENSE +0 -0
- {math_engine-0.5.0 → math_engine-0.6.0}/math_engine/ScientificEngine.py +0 -0
- {math_engine-0.5.0 → math_engine-0.6.0}/math_engine/utility.py +0 -0
- {math_engine-0.5.0 → math_engine-0.6.0}/math_engine.egg-info/SOURCES.txt +0 -0
- {math_engine-0.5.0 → math_engine-0.6.0}/math_engine.egg-info/dependency_links.txt +0 -0
- {math_engine-0.5.0 → math_engine-0.6.0}/math_engine.egg-info/top_level.txt +0 -0
- {math_engine-0.5.0 → math_engine-0.6.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.6.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
|
|
17
|
+
# Math Engine v0.6.0
|
|
18
18
|
|
|
19
19
|
[](https://pypi.org/project/math-engine/)
|
|
20
20
|
[](https://opensource.org/licenses/MIT)
|
|
@@ -354,7 +354,6 @@ All bitwise functions:
|
|
|
354
354
|
- support overflow/wrap-around behavior
|
|
355
355
|
- fully support binary, hex, decimal, and octal inputs
|
|
356
356
|
- participate in the AST just like standard operators
|
|
357
|
-
- support underscores in non-decimal literals (`0b1111_0000`)
|
|
358
357
|
|
|
359
358
|
This makes Math Engine behave like a full-featured programmer’s calculator with CPU-like precision control.
|
|
360
359
|
|
|
@@ -393,6 +392,8 @@ preset = {
|
|
|
393
392
|
# New in 0.3.0
|
|
394
393
|
"word_size": 0, # 0 = unlimited, or 8, 16, 32, 64
|
|
395
394
|
"signed_mode": True, # True = Two's Complement, False = Unsigned
|
|
395
|
+
# New in 0.6.0
|
|
396
|
+
"readable_error": False
|
|
396
397
|
}
|
|
397
398
|
|
|
398
399
|
math_engine.load_preset(preset)
|
|
@@ -412,70 +413,83 @@ decimal_places = math_engine.load_one_setting("decimal_places")
|
|
|
412
413
|
|
|
413
414
|
-----
|
|
414
415
|
|
|
415
|
-
# Error Handling
|
|
416
416
|
|
|
417
|
-
|
|
417
|
+
# Error Handling (v0.6.0: Visual & Precise)
|
|
418
418
|
|
|
419
|
-
|
|
420
|
-
* Machine-readable error code
|
|
421
|
-
* Position (if applicable)
|
|
422
|
-
* The original expression
|
|
419
|
+
Math Engine 0.6.0 introduces a dual-mode error handling system designed for both interactive use and strict library integration.
|
|
423
420
|
|
|
424
|
-
|
|
421
|
+
## 1\. Visual Feedback (Default Behavior)
|
|
422
|
+
|
|
423
|
+
By default (`readable_error = True`), the engine catches syntax errors internally and prints a visual diagnostic to the console. This is perfect for CLI tools or quick debugging, as it points exactly to the issue without crashing the program.
|
|
425
424
|
|
|
426
425
|
```python
|
|
427
426
|
import math_engine
|
|
428
|
-
from math_engine import error as E
|
|
429
427
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
except E.CalculationError as e:
|
|
433
|
-
print(e.code) # 3003
|
|
434
|
-
print(e.message) # "Division by zero"
|
|
435
|
-
print(e.equation) # "1/0"
|
|
428
|
+
# readable_error is True by default
|
|
429
|
+
math_engine.evaluate("sin(5")
|
|
436
430
|
```
|
|
437
431
|
|
|
438
|
-
|
|
432
|
+
**Console Output:**
|
|
439
433
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
| 8000 | Conversion to int failed |
|
|
447
|
-
| 8006 | Output conversion error |
|
|
434
|
+
```text
|
|
435
|
+
Errormessage: Unbalanced parenthesis.
|
|
436
|
+
Code: 2010
|
|
437
|
+
Equation: sin(5
|
|
438
|
+
^ HERE IS THE PROBLEM (Position: 5)
|
|
439
|
+
```
|
|
448
440
|
|
|
449
|
-
|
|
441
|
+
## 2\. Programmatic Handling (Exceptions)
|
|
450
442
|
|
|
451
|
-
|
|
443
|
+
If you are building an application or running unit tests, you likely want to catch exceptions instead of printing to stdout. You can disable `readable_error` to raise standard `MathError` exceptions.
|
|
452
444
|
|
|
453
|
-
|
|
445
|
+
The exception object carries **precise start and end indices**:
|
|
454
446
|
|
|
455
|
-
|
|
447
|
+
* `e.position_start` (int): Index where the error begins.
|
|
448
|
+
* `e.position_end` (int): Index where the error ends.
|
|
456
449
|
|
|
457
|
-
|
|
458
|
-
* Strict syntax rules
|
|
459
|
-
* Unit-test friendly behavior
|
|
460
|
-
* No reliance on Python’s runtime execution
|
|
450
|
+
<!-- end list -->
|
|
461
451
|
|
|
462
|
-
|
|
452
|
+
```python
|
|
453
|
+
import math_engine
|
|
454
|
+
from math_engine import error as E
|
|
455
|
+
|
|
456
|
+
# Disable visual printing to catch exceptions
|
|
457
|
+
math_engine.change_setting("readable_error", False)
|
|
458
|
+
|
|
459
|
+
try:
|
|
460
|
+
math_engine.evaluate("10.5 + 4.2.1")
|
|
461
|
+
except E.SyntaxError as e:
|
|
462
|
+
print(f"Error Code: {e.code}")
|
|
463
|
+
print(f"Location: {e.position_start} to {e.position_end}")
|
|
464
|
+
|
|
465
|
+
# You can use these indices to highlight the error in your own UI
|
|
466
|
+
bad_part = e.equation[e.position_start : e.position_end + 1]
|
|
467
|
+
print(f"Invalid segment: '{bad_part}'")
|
|
468
|
+
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
## Testing and Reliability
|
|
472
|
+
|
|
473
|
+
To write unit tests with `pytest`, ensure you set `readable_error` to `False` so that exceptions are raised and can be asserted.
|
|
463
474
|
|
|
464
475
|
```python
|
|
465
476
|
import pytest
|
|
466
477
|
import math_engine
|
|
467
478
|
from math_engine import error as E
|
|
468
479
|
|
|
469
|
-
def
|
|
480
|
+
def test_division_by_zero():
|
|
481
|
+
# Ensure exceptions are raised
|
|
482
|
+
math_engine.change_setting("readable_error", False)
|
|
483
|
+
|
|
470
484
|
with pytest.raises(E.CalculationError) as exc:
|
|
471
|
-
math_engine.evaluate("
|
|
485
|
+
math_engine.evaluate("10 / 0")
|
|
486
|
+
|
|
487
|
+
# Assert the error is Division by Zero (3003)
|
|
472
488
|
assert exc.value.code == "3003"
|
|
489
|
+
# Assert the error points exactly to the zero/operator
|
|
490
|
+
assert exc.value.position_start == 3
|
|
473
491
|
```
|
|
474
|
-
|
|
475
|
-
You can also test more advanced behavior (non-decimal, strict modes, bitwise operations, etc.) in the same way.
|
|
476
|
-
|
|
477
|
-
-----
|
|
478
|
-
|
|
492
|
+
---
|
|
479
493
|
# Performance
|
|
480
494
|
|
|
481
495
|
* No use of Python `eval()`
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
# Math Engine
|
|
2
|
+
# Math Engine v0.6.0
|
|
3
3
|
|
|
4
4
|
[](https://pypi.org/project/math-engine/)
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
@@ -339,7 +339,6 @@ All bitwise functions:
|
|
|
339
339
|
- support overflow/wrap-around behavior
|
|
340
340
|
- fully support binary, hex, decimal, and octal inputs
|
|
341
341
|
- participate in the AST just like standard operators
|
|
342
|
-
- support underscores in non-decimal literals (`0b1111_0000`)
|
|
343
342
|
|
|
344
343
|
This makes Math Engine behave like a full-featured programmer’s calculator with CPU-like precision control.
|
|
345
344
|
|
|
@@ -378,6 +377,8 @@ preset = {
|
|
|
378
377
|
# New in 0.3.0
|
|
379
378
|
"word_size": 0, # 0 = unlimited, or 8, 16, 32, 64
|
|
380
379
|
"signed_mode": True, # True = Two's Complement, False = Unsigned
|
|
380
|
+
# New in 0.6.0
|
|
381
|
+
"readable_error": False
|
|
381
382
|
}
|
|
382
383
|
|
|
383
384
|
math_engine.load_preset(preset)
|
|
@@ -397,70 +398,83 @@ decimal_places = math_engine.load_one_setting("decimal_places")
|
|
|
397
398
|
|
|
398
399
|
-----
|
|
399
400
|
|
|
400
|
-
# Error Handling
|
|
401
401
|
|
|
402
|
-
|
|
402
|
+
# Error Handling (v0.6.0: Visual & Precise)
|
|
403
403
|
|
|
404
|
-
|
|
405
|
-
* Machine-readable error code
|
|
406
|
-
* Position (if applicable)
|
|
407
|
-
* The original expression
|
|
404
|
+
Math Engine 0.6.0 introduces a dual-mode error handling system designed for both interactive use and strict library integration.
|
|
408
405
|
|
|
409
|
-
|
|
406
|
+
## 1\. Visual Feedback (Default Behavior)
|
|
407
|
+
|
|
408
|
+
By default (`readable_error = True`), the engine catches syntax errors internally and prints a visual diagnostic to the console. This is perfect for CLI tools or quick debugging, as it points exactly to the issue without crashing the program.
|
|
410
409
|
|
|
411
410
|
```python
|
|
412
411
|
import math_engine
|
|
413
|
-
from math_engine import error as E
|
|
414
412
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
except E.CalculationError as e:
|
|
418
|
-
print(e.code) # 3003
|
|
419
|
-
print(e.message) # "Division by zero"
|
|
420
|
-
print(e.equation) # "1/0"
|
|
413
|
+
# readable_error is True by default
|
|
414
|
+
math_engine.evaluate("sin(5")
|
|
421
415
|
```
|
|
422
416
|
|
|
423
|
-
|
|
417
|
+
**Console Output:**
|
|
424
418
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
| 8000 | Conversion to int failed |
|
|
432
|
-
| 8006 | Output conversion error |
|
|
419
|
+
```text
|
|
420
|
+
Errormessage: Unbalanced parenthesis.
|
|
421
|
+
Code: 2010
|
|
422
|
+
Equation: sin(5
|
|
423
|
+
^ HERE IS THE PROBLEM (Position: 5)
|
|
424
|
+
```
|
|
433
425
|
|
|
434
|
-
|
|
426
|
+
## 2\. Programmatic Handling (Exceptions)
|
|
435
427
|
|
|
436
|
-
|
|
428
|
+
If you are building an application or running unit tests, you likely want to catch exceptions instead of printing to stdout. You can disable `readable_error` to raise standard `MathError` exceptions.
|
|
437
429
|
|
|
438
|
-
|
|
430
|
+
The exception object carries **precise start and end indices**:
|
|
439
431
|
|
|
440
|
-
|
|
432
|
+
* `e.position_start` (int): Index where the error begins.
|
|
433
|
+
* `e.position_end` (int): Index where the error ends.
|
|
441
434
|
|
|
442
|
-
|
|
443
|
-
* Strict syntax rules
|
|
444
|
-
* Unit-test friendly behavior
|
|
445
|
-
* No reliance on Python’s runtime execution
|
|
435
|
+
<!-- end list -->
|
|
446
436
|
|
|
447
|
-
|
|
437
|
+
```python
|
|
438
|
+
import math_engine
|
|
439
|
+
from math_engine import error as E
|
|
440
|
+
|
|
441
|
+
# Disable visual printing to catch exceptions
|
|
442
|
+
math_engine.change_setting("readable_error", False)
|
|
443
|
+
|
|
444
|
+
try:
|
|
445
|
+
math_engine.evaluate("10.5 + 4.2.1")
|
|
446
|
+
except E.SyntaxError as e:
|
|
447
|
+
print(f"Error Code: {e.code}")
|
|
448
|
+
print(f"Location: {e.position_start} to {e.position_end}")
|
|
449
|
+
|
|
450
|
+
# You can use these indices to highlight the error in your own UI
|
|
451
|
+
bad_part = e.equation[e.position_start : e.position_end + 1]
|
|
452
|
+
print(f"Invalid segment: '{bad_part}'")
|
|
453
|
+
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
## Testing and Reliability
|
|
457
|
+
|
|
458
|
+
To write unit tests with `pytest`, ensure you set `readable_error` to `False` so that exceptions are raised and can be asserted.
|
|
448
459
|
|
|
449
460
|
```python
|
|
450
461
|
import pytest
|
|
451
462
|
import math_engine
|
|
452
463
|
from math_engine import error as E
|
|
453
464
|
|
|
454
|
-
def
|
|
465
|
+
def test_division_by_zero():
|
|
466
|
+
# Ensure exceptions are raised
|
|
467
|
+
math_engine.change_setting("readable_error", False)
|
|
468
|
+
|
|
455
469
|
with pytest.raises(E.CalculationError) as exc:
|
|
456
|
-
math_engine.evaluate("
|
|
470
|
+
math_engine.evaluate("10 / 0")
|
|
471
|
+
|
|
472
|
+
# Assert the error is Division by Zero (3003)
|
|
457
473
|
assert exc.value.code == "3003"
|
|
474
|
+
# Assert the error points exactly to the zero/operator
|
|
475
|
+
assert exc.value.position_start == 3
|
|
458
476
|
```
|
|
459
|
-
|
|
460
|
-
You can also test more advanced behavior (non-decimal, strict modes, bitwise operations, etc.) in the same way.
|
|
461
|
-
|
|
462
|
-
-----
|
|
463
|
-
|
|
477
|
+
---
|
|
464
478
|
# Performance
|
|
465
479
|
|
|
466
480
|
* No use of Python `eval()`
|
|
@@ -8,11 +8,13 @@ from . import error as E
|
|
|
8
8
|
class Number:
|
|
9
9
|
"""AST node for numeric literal backed by Decimal."""
|
|
10
10
|
|
|
11
|
-
def __init__(self, value):
|
|
11
|
+
def __init__(self, value, position_start=-1, position_end=-1):
|
|
12
12
|
# Always normalize input to Decimal via string to avoid float artifacts
|
|
13
13
|
if not isinstance(value, Decimal):
|
|
14
14
|
value = str(value)
|
|
15
15
|
self.value = Decimal(value)
|
|
16
|
+
self.position_start = position_start
|
|
17
|
+
self.position_end = position_end
|
|
16
18
|
|
|
17
19
|
def evaluate(self):
|
|
18
20
|
"""Return Decimal value for this literal."""
|
|
@@ -23,11 +25,9 @@ class Number:
|
|
|
23
25
|
return (0, self.value)
|
|
24
26
|
|
|
25
27
|
def __repr__(self):
|
|
26
|
-
# Helpful for debugging/printing the AST
|
|
27
28
|
try:
|
|
28
29
|
display_value = self.value.to_normal_string()
|
|
29
30
|
except AttributeError:
|
|
30
|
-
# Fallback for older Decimal versions
|
|
31
31
|
display_value = str(self.value)
|
|
32
32
|
return f"Number({display_value})"
|
|
33
33
|
|
|
@@ -35,21 +35,21 @@ class Number:
|
|
|
35
35
|
class Variable:
|
|
36
36
|
"""AST node representing a single symbolic variable (e.g. 'var0')."""
|
|
37
37
|
|
|
38
|
-
def __init__(self, name):
|
|
38
|
+
def __init__(self, name, position_start=-1, position_end=-1):
|
|
39
39
|
self.name = name
|
|
40
|
+
self.position_start = position_start
|
|
41
|
+
self.position_end = position_end
|
|
40
42
|
|
|
41
43
|
def evaluate(self):
|
|
42
44
|
"""Variables cannot be directly evaluated without solving."""
|
|
43
|
-
raise E.SolverError(f"Non linear problem.", code="3005")
|
|
45
|
+
raise E.SolverError(f"Non linear problem.", code="3005", position_start=self.position_start)
|
|
44
46
|
|
|
45
47
|
def collect_term(self, var_name):
|
|
46
48
|
"""Return (1, 0) if this variable matches var_name; else error."""
|
|
47
49
|
if self.name == var_name:
|
|
48
50
|
return (1, 0)
|
|
49
51
|
else:
|
|
50
|
-
|
|
51
|
-
raise E.SolverError(f"Multiple variables found: {self.name}", code="3002")
|
|
52
|
-
return (0, 0)
|
|
52
|
+
raise E.SolverError(f"Multiple variables found: {self.name}", code="3002", position_start=self.position_start)
|
|
53
53
|
|
|
54
54
|
def __repr__(self):
|
|
55
55
|
return f"Variable('{self.name}')"
|
|
@@ -58,15 +58,20 @@ class Variable:
|
|
|
58
58
|
class BinOp:
|
|
59
59
|
"""AST node for a binary operation: left <operator> right."""
|
|
60
60
|
|
|
61
|
-
def __init__(self, left, operator, right):
|
|
61
|
+
def __init__(self, left, operator, right, position_start=-1, position_end=-1):
|
|
62
62
|
self.left = left
|
|
63
63
|
self.operator = operator
|
|
64
64
|
self.right = right
|
|
65
|
+
self.position_start = position_start
|
|
66
|
+
self.position_end = position_end
|
|
65
67
|
|
|
66
68
|
def evaluate(self):
|
|
67
69
|
"""Evaluate numeric subtree and apply the binary operator."""
|
|
68
70
|
left_value = self.left.evaluate()
|
|
69
71
|
right_value = self.right.evaluate()
|
|
72
|
+
def check_int(val_l, val_r):
|
|
73
|
+
if val_l % 1 != 0 or val_r % 1 != 0:
|
|
74
|
+
raise E.CalculationError(f"Operator '{self.operator}' requires integers.", code="3042", position_start=self.position_start)
|
|
70
75
|
|
|
71
76
|
if self.operator == '+':
|
|
72
77
|
return left_value + right_value
|
|
@@ -75,28 +80,23 @@ class BinOp:
|
|
|
75
80
|
return left_value - right_value
|
|
76
81
|
|
|
77
82
|
elif self.operator == '&':
|
|
78
|
-
|
|
79
|
-
raise E.CalculationError("Bitwise AND requires integers.", code="3042")
|
|
83
|
+
check_int(left_value, right_value)
|
|
80
84
|
return Decimal(int(left_value) & int(right_value))
|
|
81
85
|
|
|
82
86
|
elif self.operator == '|':
|
|
83
|
-
|
|
84
|
-
raise E.CalculationError("Bitwise OR requires integers.", code="3042")
|
|
87
|
+
check_int(left_value, right_value)
|
|
85
88
|
return Decimal(int(left_value) | int(right_value))
|
|
86
89
|
|
|
87
90
|
elif self.operator == '^':
|
|
88
|
-
|
|
89
|
-
raise E.CalculationError("XOR requires integers.", code="3042")
|
|
91
|
+
check_int(left_value, right_value)
|
|
90
92
|
return Decimal(int(left_value) ^ int(right_value))
|
|
91
93
|
|
|
92
94
|
elif self.operator == '<<':
|
|
93
|
-
|
|
94
|
-
raise E.CalculationError("Bitshift requires integers.", code="3041")
|
|
95
|
+
check_int(left_value, right_value)
|
|
95
96
|
return Decimal(int(left_value) << int(right_value))
|
|
96
97
|
|
|
97
98
|
elif self.operator == '>>':
|
|
98
|
-
|
|
99
|
-
raise E.CalculationError("Bitshift requires integers.", code="3041")
|
|
99
|
+
check_int(left_value, right_value)
|
|
100
100
|
return Decimal(int(left_value) >> int(right_value))
|
|
101
101
|
|
|
102
102
|
elif self.operator == '*':
|
|
@@ -107,78 +107,55 @@ class BinOp:
|
|
|
107
107
|
|
|
108
108
|
elif self.operator == '/':
|
|
109
109
|
if right_value == 0:
|
|
110
|
-
raise E.CalculationError("Division by zero", code="3003")
|
|
110
|
+
raise E.CalculationError("Division by zero", code="3003", position_start=self.position_start)
|
|
111
111
|
return left_value / right_value
|
|
112
112
|
|
|
113
113
|
elif self.operator == '=':
|
|
114
|
-
# Equality is evaluated to a boolean (used for "= True/False" responses)
|
|
115
114
|
return left_value == right_value
|
|
116
115
|
else:
|
|
117
|
-
raise E.CalculationError(f"Unknown operator: {self.operator}", code="3004")
|
|
116
|
+
raise E.CalculationError(f"Unknown operator: {self.operator}", code="3004", position_start=self.position_start)
|
|
118
117
|
|
|
119
118
|
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
|
-
"""
|
|
119
|
+
"""Collect linear terms on this subtree into (factor_of_var, constant)."""
|
|
124
120
|
(left_factor, left_constant) = self.left.collect_term(var_name)
|
|
125
121
|
(right_factor, right_constant) = self.right.collect_term(var_name)
|
|
126
122
|
|
|
127
123
|
if self.operator == '+':
|
|
128
|
-
|
|
129
|
-
result_constant = left_constant + right_constant
|
|
130
|
-
return (result_factor, result_constant)
|
|
124
|
+
return (left_factor + right_factor, left_constant + right_constant)
|
|
131
125
|
|
|
132
126
|
elif self.operator == '-':
|
|
133
|
-
|
|
134
|
-
result_constant = left_constant - right_constant
|
|
135
|
-
return (result_factor, result_constant)
|
|
127
|
+
return (left_factor - right_factor, left_constant - right_constant)
|
|
136
128
|
|
|
137
129
|
elif self.operator == '*':
|
|
138
|
-
# Only constant * (A*x + B) is allowed.
|
|
130
|
+
# Only constant * (A*x + B) is allowed.
|
|
139
131
|
if left_factor != 0 and right_factor != 0:
|
|
140
|
-
raise E.SyntaxError("x^x Error.", code="3005")
|
|
132
|
+
raise E.SyntaxError("x^x Error (Non-linear).", code="3005", position_start=self.position_start)
|
|
141
133
|
|
|
142
134
|
elif left_factor == 0:
|
|
143
|
-
|
|
144
|
-
result_factor = left_constant * right_factor
|
|
145
|
-
result_constant = left_constant * right_constant
|
|
146
|
-
return (result_factor, result_constant)
|
|
135
|
+
return (left_constant * right_factor, left_constant * right_constant)
|
|
147
136
|
|
|
148
137
|
elif right_factor == 0:
|
|
149
|
-
|
|
150
|
-
result_factor = right_constant * left_factor
|
|
151
|
-
result_constant = right_constant * left_constant
|
|
152
|
-
return (result_factor, result_constant)
|
|
138
|
+
return (right_constant * left_factor, right_constant * left_constant)
|
|
153
139
|
|
|
154
140
|
elif left_factor == 0 and right_factor == 0:
|
|
155
|
-
|
|
156
|
-
result_factor = 0
|
|
157
|
-
result_constant = right_constant * left_constant
|
|
158
|
-
return (result_factor, result_constant)
|
|
141
|
+
return (0, right_constant * left_constant)
|
|
159
142
|
|
|
160
143
|
elif self.operator == '/':
|
|
161
|
-
# (A*x + B) / D is allowed; division by (C*x + D) is non-linear
|
|
162
144
|
if right_factor != 0:
|
|
163
|
-
raise E.SolverError("Non-linear equation
|
|
145
|
+
raise E.SolverError("Non-linear equation (Division by variable).", code="3006", position_start=self.position_start)
|
|
164
146
|
elif right_constant == 0:
|
|
165
|
-
raise E.SolverError("Solver: Division by zero", code="3003")
|
|
147
|
+
raise E.SolverError("Solver: Division by zero", code="3003", position_start=self.position_start)
|
|
166
148
|
else:
|
|
167
|
-
|
|
168
|
-
result_factor = left_factor / right_constant
|
|
169
|
-
result_constant = left_constant / right_constant
|
|
170
|
-
return (result_factor, result_constant)
|
|
149
|
+
return (left_factor / right_constant, left_constant / right_constant)
|
|
171
150
|
|
|
172
151
|
elif self.operator == '**':
|
|
173
|
-
|
|
174
|
-
raise E.SolverError("Powers are not supported by the linear solver.", code="3007")
|
|
152
|
+
raise E.SolverError("Powers are not supported by the linear solver.", code="3007", position_start=self.position_start)
|
|
175
153
|
|
|
176
154
|
elif self.operator == '=':
|
|
177
|
-
|
|
178
|
-
raise E.SolverError("Should not happen: '=' inside collect_terms", code="3720")
|
|
155
|
+
raise E.SolverError("Should not happen: '=' inside collect_terms", code="3720", position_start=self.position_start)
|
|
179
156
|
|
|
180
157
|
else:
|
|
181
|
-
raise E.CalculationError(f"Unknown operator: {self.operator}", code="3004")
|
|
158
|
+
raise E.CalculationError(f"Unknown operator: {self.operator}", code="3004", position_start=self.position_start)
|
|
182
159
|
|
|
183
160
|
def __repr__(self):
|
|
184
|
-
return f"BinOp({self.operator!r}, left={self.left}, right={self.right})"
|
|
161
|
+
return f"BinOp({self.operator!r}, left={self.left}, right={self.right})"
|