nexuscalc 1.0.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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Light Bulb Experiments
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,126 @@
1
+ Metadata-Version: 2.1
2
+ Name: nexuscalc
3
+ Version: 1.0.0
4
+ Summary: A powerful calculator application with an interactive CLI
5
+ Home-page: https://github.com/yourusername/nexuscalc
6
+ Author: NexusCalc Team
7
+ License: UNKNOWN
8
+ Platform: UNKNOWN
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.6
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+
16
+ # NexusCalc
17
+
18
+ A powerful calculator application with an interactive command-line interface.
19
+
20
+ ## Features
21
+
22
+ - ➕ Addition
23
+ - ➖ Subtraction
24
+ - ✖️ Multiplication
25
+ - ➗ Division with zero division error handling
26
+ - 🏠 Floor division
27
+ - 🔢 Mathematical constants (pi, e, tau)
28
+ - 📐 Advanced mathematical functions (sqrt, sin, cos, tan, log)
29
+ - 🛡️ Comprehensive error handling
30
+ - 💎 Clean number formatting
31
+ - 🎨 Colorful and interactive CLI
32
+
33
+ ## Installation
34
+
35
+ ```bash
36
+ python -m pip install nexuscalc
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ ### Interactive Mode
42
+
43
+ ```python
44
+ from nexuscalc import nexuscalc
45
+ nexuscalc.calculate()
46
+ ```
47
+
48
+ Or:
49
+
50
+ ```python
51
+ import nexuscalc
52
+ nexuscalc.nexuscalc.calculate()
53
+ ```
54
+
55
+ Or after installation:
56
+
57
+ ```bash
58
+ nexuscalc
59
+ ```
60
+
61
+ ### Programmatic Usage
62
+
63
+ ```python
64
+ from nexuscalc.core.operations import Operations
65
+
66
+ ops = Operations()
67
+ result = ops.add(5, 3) # Returns 8
68
+ ```
69
+
70
+ ### Advanced Expression Evaluation
71
+
72
+ ```python
73
+ from nexuscalc.evaluator.evaluator import Evaluator
74
+
75
+ eval = Evaluator()
76
+ result = eval.evaluate_expression("2 + 3 * 4") # Returns 14
77
+ ```
78
+
79
+ ## Error Handling
80
+
81
+ NexusCalc includes comprehensive error handling for:
82
+
83
+ - Division by zero (with friendly error messages)
84
+ - Invalid input formats
85
+ - Numeric overflow
86
+ - Expression parsing errors
87
+ - Function evaluation errors
88
+
89
+ ## Development
90
+
91
+ Run tests:
92
+
93
+ ```bash
94
+ python -m pytest tests/ -v
95
+ ```
96
+
97
+ Run tests with coverage:
98
+
99
+ ```bash
100
+ python -m pytest tests/ --cov=src/nexuscalc
101
+ ```
102
+
103
+ ## Project Structure
104
+
105
+ ```
106
+ nexuscalc/
107
+ ├── src/nexuscalc/
108
+ │ ├── core/ # Core calculator logic
109
+ │ ├── parsers/ # Expression parsing
110
+ │ ├── evaluator/ # Expression evaluation
111
+ │ ├── utils/ # Utilities and helpers
112
+ │ └── exceptions/ # Custom exceptions
113
+ ├── tests/ # Unit tests
114
+ ├── examples/ # Usage examples
115
+ └── setup.py # Package setup
116
+ ```
117
+
118
+ ## License
119
+
120
+ MIT
121
+
122
+ ## Author
123
+
124
+ Light Bulb Experiments © 2026
125
+
126
+
@@ -0,0 +1,109 @@
1
+ # NexusCalc
2
+
3
+ A powerful calculator application with an interactive command-line interface.
4
+
5
+ ## Features
6
+
7
+ - ➕ Addition
8
+ - ➖ Subtraction
9
+ - ✖️ Multiplication
10
+ - ➗ Division with zero division error handling
11
+ - 🏠 Floor division
12
+ - 🔢 Mathematical constants (pi, e, tau)
13
+ - 📐 Advanced mathematical functions (sqrt, sin, cos, tan, log)
14
+ - 🛡️ Comprehensive error handling
15
+ - 💎 Clean number formatting
16
+ - 🎨 Colorful and interactive CLI
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ python -m pip install nexuscalc
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ ### Interactive Mode
27
+
28
+ ```python
29
+ from nexuscalc import nexuscalc
30
+ nexuscalc.calculate()
31
+ ```
32
+
33
+ Or:
34
+
35
+ ```python
36
+ import nexuscalc
37
+ nexuscalc.nexuscalc.calculate()
38
+ ```
39
+
40
+ Or after installation:
41
+
42
+ ```bash
43
+ nexuscalc
44
+ ```
45
+
46
+ ### Programmatic Usage
47
+
48
+ ```python
49
+ from nexuscalc.core.operations import Operations
50
+
51
+ ops = Operations()
52
+ result = ops.add(5, 3) # Returns 8
53
+ ```
54
+
55
+ ### Advanced Expression Evaluation
56
+
57
+ ```python
58
+ from nexuscalc.evaluator.evaluator import Evaluator
59
+
60
+ eval = Evaluator()
61
+ result = eval.evaluate_expression("2 + 3 * 4") # Returns 14
62
+ ```
63
+
64
+ ## Error Handling
65
+
66
+ NexusCalc includes comprehensive error handling for:
67
+
68
+ - Division by zero (with friendly error messages)
69
+ - Invalid input formats
70
+ - Numeric overflow
71
+ - Expression parsing errors
72
+ - Function evaluation errors
73
+
74
+ ## Development
75
+
76
+ Run tests:
77
+
78
+ ```bash
79
+ python -m pytest tests/ -v
80
+ ```
81
+
82
+ Run tests with coverage:
83
+
84
+ ```bash
85
+ python -m pytest tests/ --cov=src/nexuscalc
86
+ ```
87
+
88
+ ## Project Structure
89
+
90
+ ```
91
+ nexuscalc/
92
+ ├── src/nexuscalc/
93
+ │ ├── core/ # Core calculator logic
94
+ │ ├── parsers/ # Expression parsing
95
+ │ ├── evaluator/ # Expression evaluation
96
+ │ ├── utils/ # Utilities and helpers
97
+ │ └── exceptions/ # Custom exceptions
98
+ ├── tests/ # Unit tests
99
+ ├── examples/ # Usage examples
100
+ └── setup.py # Package setup
101
+ ```
102
+
103
+ ## License
104
+
105
+ MIT
106
+
107
+ ## Author
108
+
109
+ Light Bulb Experiments © 2026
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,29 @@
1
+ """Setup configuration for NexusCalc."""
2
+
3
+ from setuptools import setup, find_packages
4
+
5
+ with open("README.md", "r", encoding="utf-8") as fh:
6
+ long_description = fh.read()
7
+
8
+ setup(
9
+ name="nexuscalc",
10
+ version="1.0.0",
11
+ author="NexusCalc Team",
12
+ description="A powerful calculator application with an interactive CLI",
13
+ long_description=long_description,
14
+ long_description_content_type="text/markdown",
15
+ url="https://github.com/yourusername/nexuscalc",
16
+ package_dir={"": "src"},
17
+ packages=find_packages(where="src"),
18
+ classifiers=[
19
+ "Programming Language :: Python :: 3",
20
+ "License :: OSI Approved :: MIT License",
21
+ "Operating System :: OS Independent",
22
+ ],
23
+ python_requires=">=3.6",
24
+ entry_points={
25
+ "console_scripts": [
26
+ "nexuscalc=nexuscalc:calculate",
27
+ ],
28
+ },
29
+ )
@@ -0,0 +1,23 @@
1
+ """NexusCalc - A powerful calculator application."""
2
+
3
+ from .core.calculator import NexusCalc
4
+
5
+ __version__ = "1.0.0"
6
+ __all__ = ["NexusCalc", "calculate", "nexuscalc"]
7
+
8
+ def calculate():
9
+ """Main entry point for the calculator."""
10
+ calc = NexusCalc()
11
+ calc.run()
12
+
13
+ # Create a class that matches the module name for nexuscalc.nexuscalc.calculate()
14
+ class nexuscalc:
15
+ """Wrapper class for nexuscalc.nexuscalc.calculate() compatibility."""
16
+ @staticmethod
17
+ def calculate():
18
+ """Main entry point for the calculator."""
19
+ calc = NexusCalc()
20
+ calc.run()
21
+
22
+ # Also expose NexusCalc as a class
23
+ NexusCalc = NexusCalc
@@ -0,0 +1 @@
1
+ """Core calculator module."""
@@ -0,0 +1,140 @@
1
+ """Main calculator implementation."""
2
+
3
+ import sys
4
+ import re
5
+ from ..core.operations import Operations
6
+ from ..utils.validators import validate_number
7
+ from ..utils.formatters import format_result
8
+ from ..exceptions.calculator_errors import CalculatorError, DivisionByZeroError
9
+
10
+ class NexusCalc:
11
+ """Main calculator class with interactive menu."""
12
+
13
+ def __init__(self):
14
+ self.operations = Operations()
15
+ self.running = True
16
+ self.menu_options = {
17
+ '1': ('Add', self.operations.add),
18
+ '2': ('Subtract', self.operations.subtract),
19
+ '3': ('Multiply', self.operations.multiply),
20
+ '4': ('Divide', self.operations.divide),
21
+ '5': ('Floor', self.operations.floor_divide),
22
+ }
23
+ # Regex patterns for quit commands
24
+ self.quit_patterns = [
25
+ re.compile(r'^[Qq]$'), # q or Q
26
+ re.compile(r'^[Qq][Uu][Ii][Tt]$'), # quit in any case
27
+ re.compile(r'^6$'), # number 6
28
+ ]
29
+
30
+ def is_quit_command(self, text):
31
+ """Check if the input matches any quit pattern."""
32
+ if not text:
33
+ return False
34
+ text = text.strip()
35
+ for pattern in self.quit_patterns:
36
+ if pattern.match(text):
37
+ return True
38
+ return False
39
+
40
+ def run(self):
41
+ """Run the calculator main loop."""
42
+ try:
43
+ while self.running:
44
+ try:
45
+ self.show_menu()
46
+ choice = self.get_input("Use 1-5").strip().lower()
47
+
48
+ # Check if it's a quit command using regex
49
+ if self.is_quit_command(choice):
50
+ print("\n👋 Goodbye! Thanks for using NexusCalc!")
51
+ self.running = False
52
+ break
53
+
54
+ if choice not in self.menu_options:
55
+ print("❌ Invalid choice. Please select 1-5 or 6/q/quit to exit.")
56
+ continue
57
+
58
+ operation_name, operation_func = self.menu_options[choice]
59
+
60
+ try:
61
+ num1 = self.get_number("Enter first number")
62
+ num2 = self.get_number("Enter second number")
63
+
64
+ result = operation_func(num1, num2)
65
+ print(f"\n✅ Result: {format_result(result)}")
66
+
67
+ except DivisionByZeroError as e:
68
+ print(f"\n❌ Division Error: {e}")
69
+ print("💡 Hint: You cannot divide by zero. Please try a different number.")
70
+ except CalculatorError as e:
71
+ print(f"\n❌ Error: {e}")
72
+ except ValueError:
73
+ print("\n❌ Error: Invalid number format. Please enter a valid number.")
74
+ except Exception as e:
75
+ print(f"\n❌ Unexpected error: {e}")
76
+
77
+ print()
78
+
79
+ except KeyboardInterrupt:
80
+ print("\n\n⚠️ Operation cancelled by user.")
81
+ print("💡 Press Ctrl+C again to exit, or continue using the calculator.")
82
+ continue
83
+ except EOFError:
84
+ print("\n\n⚠️ Input stream closed.")
85
+ print("💡 Exiting calculator...")
86
+ self.running = False
87
+ break
88
+
89
+ except KeyboardInterrupt:
90
+ print("\n\n👋 Goodbye! Calculator exited.")
91
+ sys.exit(0)
92
+ except Exception as e:
93
+ print(f"\n❌ Fatal error: {e}")
94
+ sys.exit(1)
95
+
96
+ def show_menu(self):
97
+ """Display the main menu."""
98
+ print("\n" + "="*50)
99
+ print("🔢 NEXUSCALC - Powerful Calculator")
100
+ print("="*50)
101
+ print("\nWhat are you trying to calculate?")
102
+ print("List:")
103
+ print()
104
+ print("1. Add ➕")
105
+ print("2. Subtract ➖")
106
+ print("3. Multiply ✖️")
107
+ print("4. Divide ➗")
108
+ print("5. Floor 🏠")
109
+ print("6. Quit 🚪")
110
+ print()
111
+ print("💡 Press Ctrl+C to cancel operation")
112
+ print("💡 Type '6', 'q', 'Q', 'quit', or 'QUIT' to exit")
113
+ print("-"*50)
114
+
115
+ def get_input(self, prompt):
116
+ """Get user input with NEXUSCALC > prompt."""
117
+ try:
118
+ return input(f"{prompt}\nNEXUSCALC > ")
119
+ except KeyboardInterrupt:
120
+ print("\n\n⚠️ Input cancelled.")
121
+ raise
122
+ except EOFError:
123
+ print("\n\n⚠️ End of input detected.")
124
+ raise
125
+
126
+ def get_number(self, prompt):
127
+ """Get and validate a number from user input."""
128
+ while True:
129
+ try:
130
+ user_input = self.get_input(prompt)
131
+ return validate_number(user_input)
132
+ except CalculatorError as e:
133
+ print(f"❌ Error: {e}")
134
+ print("💡 Please try again with a valid number.")
135
+ except KeyboardInterrupt:
136
+ print("\n⚠️ Number input cancelled.")
137
+ raise
138
+ except EOFError:
139
+ print("\n⚠️ End of input detected.")
140
+ raise
@@ -0,0 +1,30 @@
1
+ """Mathematical constants."""
2
+
3
+ import math
4
+
5
+ class Constants:
6
+ """Collection of mathematical constants."""
7
+
8
+ PI = math.pi
9
+ E = math.e
10
+ TAU = math.tau
11
+ INF = float('inf')
12
+ NAN = float('nan')
13
+
14
+ @classmethod
15
+ def get_constant(cls, name):
16
+ """Get a constant by name."""
17
+ constants = {
18
+ 'pi': cls.PI,
19
+ 'e': cls.E,
20
+ 'tau': cls.TAU,
21
+ 'inf': cls.INF,
22
+ 'infinity': cls.INF,
23
+ 'nan': cls.NAN
24
+ }
25
+ return constants.get(name.lower())
26
+
27
+ @classmethod
28
+ def list_constants(cls):
29
+ """List all available constants."""
30
+ return ['pi', 'e', 'tau', 'inf', 'nan']
@@ -0,0 +1,55 @@
1
+ """Mathematical operations with proper error handling."""
2
+
3
+ from ..exceptions.calculator_errors import DivisionByZeroError, CalculatorError
4
+
5
+ class Operations:
6
+ """Collection of mathematical operations."""
7
+
8
+ @staticmethod
9
+ def add(a, b):
10
+ """Add two numbers."""
11
+ return a + b
12
+
13
+ @staticmethod
14
+ def subtract(a, b):
15
+ """Subtract b from a."""
16
+ return a - b
17
+
18
+ @staticmethod
19
+ def multiply(a, b):
20
+ """Multiply two numbers."""
21
+ return a * b
22
+
23
+ @staticmethod
24
+ def divide(a, b):
25
+ """Divide a by b with zero division handling."""
26
+ if b == 0:
27
+ raise DivisionByZeroError("Cannot divide by zero!")
28
+ if a == 0:
29
+ return 0.0
30
+ return a / b
31
+
32
+ @staticmethod
33
+ def floor_divide(a, b):
34
+ """Floor divide a by b with zero division handling."""
35
+ if b == 0:
36
+ raise DivisionByZeroError("Cannot floor divide by zero!")
37
+ if a == 0:
38
+ return 0
39
+ return a // b
40
+
41
+ @staticmethod
42
+ def safe_divide(a, b):
43
+ """Safe division that returns None on error instead of raising."""
44
+ try:
45
+ return a / b
46
+ except ZeroDivisionError:
47
+ return None
48
+
49
+ @staticmethod
50
+ def safe_floor_divide(a, b):
51
+ """Safe floor division that returns None on error instead of raising."""
52
+ try:
53
+ return a // b
54
+ except ZeroDivisionError:
55
+ return None
@@ -0,0 +1 @@
1
+ """Evaluator modules."""
@@ -0,0 +1,81 @@
1
+ """Expression evaluator for advanced calculations."""
2
+
3
+ import math
4
+ from ..exceptions.calculator_errors import EvaluationError, DivisionByZeroError
5
+ from ..parsers.ast_nodes import NumberNode, BinOpNode, UnaryOpNode, FunctionNode, ConstantNode
6
+ from ..core.constants import Constants
7
+
8
+ class Evaluator:
9
+ """Evaluate parsed expressions."""
10
+
11
+ def __init__(self):
12
+ self.constants = Constants()
13
+ self.functions = {
14
+ 'sqrt': math.sqrt,
15
+ 'sin': math.sin,
16
+ 'cos': math.cos,
17
+ 'tan': math.tan,
18
+ 'log': math.log,
19
+ 'abs': abs,
20
+ 'ceil': math.ceil,
21
+ 'floor': math.floor,
22
+ 'round': round,
23
+ }
24
+
25
+ def evaluate(self, ast):
26
+ """Evaluate an AST and return the result."""
27
+ if isinstance(ast, NumberNode):
28
+ return ast.value
29
+
30
+ elif isinstance(ast, BinOpNode):
31
+ left_val = self.evaluate(ast.left)
32
+ right_val = self.evaluate(ast.right)
33
+
34
+ if ast.operator == '+':
35
+ return left_val + right_val
36
+ elif ast.operator == '-':
37
+ return left_val - right_val
38
+ elif ast.operator == '*':
39
+ return left_val * right_val
40
+ elif ast.operator == '/':
41
+ if right_val == 0:
42
+ raise DivisionByZeroError("Division by zero in expression")
43
+ return left_val / right_val
44
+ elif ast.operator == '//':
45
+ if right_val == 0:
46
+ raise DivisionByZeroError("Floor division by zero in expression")
47
+ return left_val // right_val
48
+ else:
49
+ raise EvaluationError(f"Unknown binary operator: {ast.operator}")
50
+
51
+ elif isinstance(ast, UnaryOpNode):
52
+ operand_val = self.evaluate(ast.operand)
53
+ if ast.operator == '-':
54
+ return -operand_val
55
+ elif ast.operator == '+':
56
+ return +operand_val
57
+ else:
58
+ raise EvaluationError(f"Unknown unary operator: {ast.operator}")
59
+
60
+ elif isinstance(ast, FunctionNode):
61
+ args = [self.evaluate(arg) for arg in ast.args]
62
+ if ast.name in self.functions:
63
+ try:
64
+ return self.functions[ast.name](*args)
65
+ except Exception as e:
66
+ raise EvaluationError(f"Error evaluating function {ast.name}: {e}")
67
+ else:
68
+ raise EvaluationError(f"Unknown function: {ast.name}")
69
+
70
+ elif isinstance(ast, ConstantNode):
71
+ return ast.value
72
+
73
+ else:
74
+ raise EvaluationError(f"Unknown AST node type: {type(ast)}")
75
+
76
+ def evaluate_expression(self, expression):
77
+ """Convenience method to parse and evaluate an expression."""
78
+ from ..parsers.expression_parser import ExpressionParser
79
+ parser = ExpressionParser()
80
+ ast = parser.parse(expression)
81
+ return self.evaluate(ast)
@@ -0,0 +1,124 @@
1
+ """Custom mathematical functions."""
2
+
3
+ import math
4
+ from functools import wraps
5
+ from nexuscalc.exceptions.calculator_errors import CalculatorError
6
+
7
+ class MathFunctions:
8
+ """Collection of mathematical functions."""
9
+
10
+ @staticmethod
11
+ def sqrt(x):
12
+ """Square root."""
13
+ if x < 0:
14
+ raise CalculatorError("Cannot take square root of negative number")
15
+ return math.sqrt(x)
16
+
17
+ @staticmethod
18
+ def sin(x):
19
+ """Sine in radians."""
20
+ return math.sin(x)
21
+
22
+ @staticmethod
23
+ def cos(x):
24
+ """Cosine in radians."""
25
+ return math.cos(x)
26
+
27
+ @staticmethod
28
+ def tan(x):
29
+ """Tangent in radians."""
30
+ return math.tan(x)
31
+
32
+ @staticmethod
33
+ def log(x, base=math.e):
34
+ """Logarithm with specified base."""
35
+ if x <= 0:
36
+ raise CalculatorError("Logarithm only defined for positive numbers")
37
+ if base <= 0 or base == 1:
38
+ raise CalculatorError("Logarithm base must be positive and not equal to 1")
39
+ return math.log(x, base)
40
+
41
+ @staticmethod
42
+ def log10(x):
43
+ """Base-10 logarithm."""
44
+ return MathFunctions.log(x, 10)
45
+
46
+ @staticmethod
47
+ def log2(x):
48
+ """Base-2 logarithm."""
49
+ return MathFunctions.log(x, 2)
50
+
51
+ @staticmethod
52
+ def exp(x):
53
+ """Exponential function e^x."""
54
+ return math.exp(x)
55
+
56
+ @staticmethod
57
+ def pow(x, y):
58
+ """Power function x^y."""
59
+ return x ** y
60
+
61
+ @staticmethod
62
+ def factorial(x):
63
+ """Factorial function."""
64
+ if x < 0:
65
+ raise CalculatorError("Factorial not defined for negative numbers")
66
+ if not isinstance(x, int) and x.is_integer():
67
+ x = int(x)
68
+ if not isinstance(x, int):
69
+ raise CalculatorError("Factorial only defined for integers")
70
+ return math.factorial(x)
71
+
72
+ @staticmethod
73
+ def gcd(a, b):
74
+ """Greatest common divisor."""
75
+ return math.gcd(int(a), int(b))
76
+
77
+ @staticmethod
78
+ def lcm(a, b):
79
+ """Least common multiple."""
80
+ return abs(a * b) // math.gcd(int(a), int(b))
81
+
82
+ @staticmethod
83
+ def max(*args):
84
+ """Maximum value."""
85
+ return max(args)
86
+
87
+ @staticmethod
88
+ def min(*args):
89
+ """Minimum value."""
90
+ return min(args)
91
+
92
+ @staticmethod
93
+ def average(*args):
94
+ """Average value."""
95
+ if not args:
96
+ raise CalculatorError("At least one number required for average")
97
+ return sum(args) / len(args)
98
+
99
+ @staticmethod
100
+ def sum(*args):
101
+ """Sum of numbers."""
102
+ return sum(args)
103
+
104
+ @staticmethod
105
+ def product(*args):
106
+ """Product of numbers."""
107
+ result = 1
108
+ for arg in args:
109
+ result *= arg
110
+ return result
111
+
112
+ @staticmethod
113
+ def is_prime(n):
114
+ """Check if number is prime."""
115
+ if n < 2:
116
+ return False
117
+ if n == 2:
118
+ return True
119
+ if n % 2 == 0:
120
+ return False
121
+ for i in range(3, int(math.sqrt(n)) + 1, 2):
122
+ if n % i == 0:
123
+ return False
124
+ return True
@@ -0,0 +1 @@
1
+ """Exception modules."""
@@ -0,0 +1,27 @@
1
+ """Custom exceptions for the calculator."""
2
+
3
+ class CalculatorError(Exception):
4
+ """Base exception for calculator errors."""
5
+ pass
6
+
7
+ class DivisionByZeroError(CalculatorError):
8
+ """Exception raised when attempting to divide by zero."""
9
+ def __init__(self, message="Cannot divide by zero!"):
10
+ self.message = message
11
+ super().__init__(self.message)
12
+
13
+ class ParserError(CalculatorError):
14
+ """Exception raised for parsing errors."""
15
+ pass
16
+
17
+ class EvaluationError(CalculatorError):
18
+ """Exception raised for evaluation errors."""
19
+ pass
20
+
21
+ class InvalidInputError(CalculatorError):
22
+ """Exception raised for invalid input."""
23
+ pass
24
+
25
+ class OverflowError(CalculatorError):
26
+ """Exception raised for numeric overflow."""
27
+ pass
@@ -0,0 +1 @@
1
+ """Parser modules."""
@@ -0,0 +1,63 @@
1
+ """AST nodes for parsed expressions."""
2
+
3
+ from dataclasses import dataclass
4
+ from typing import List, Any, Optional
5
+
6
+ @dataclass
7
+ class Token:
8
+ """Token representation."""
9
+ type: str
10
+ value: Any
11
+ position: int = 0
12
+ line: int = 1
13
+ col: int = 1
14
+
15
+ class ASTNode:
16
+ """Base class for AST nodes."""
17
+ pass
18
+
19
+ @dataclass
20
+ class NumberNode(ASTNode):
21
+ """Node for numeric values."""
22
+ value: float
23
+
24
+ def __str__(self):
25
+ return str(self.value)
26
+
27
+ @dataclass
28
+ class BinOpNode(ASTNode):
29
+ """Node for binary operations."""
30
+ operator: str
31
+ left: ASTNode
32
+ right: ASTNode
33
+
34
+ def __str__(self):
35
+ return f"({self.left} {self.operator} {self.right})"
36
+
37
+ @dataclass
38
+ class UnaryOpNode(ASTNode):
39
+ """Node for unary operations."""
40
+ operator: str
41
+ operand: ASTNode
42
+
43
+ def __str__(self):
44
+ return f"({self.operator}{self.operand})"
45
+
46
+ @dataclass
47
+ class FunctionNode(ASTNode):
48
+ """Node for function calls."""
49
+ name: str
50
+ args: List[ASTNode]
51
+
52
+ def __str__(self):
53
+ args_str = ', '.join(str(arg) for arg in self.args)
54
+ return f"{self.name}({args_str})"
55
+
56
+ @dataclass
57
+ class ConstantNode(ASTNode):
58
+ """Node for constants like pi, e."""
59
+ name: str
60
+ value: float
61
+
62
+ def __str__(self):
63
+ return self.name
@@ -0,0 +1,111 @@
1
+ """Expression parser for advanced calculations."""
2
+
3
+ import re
4
+ from ..exceptions.calculator_errors import ParserError
5
+ from .tokenizer import Tokenizer
6
+ from .ast_nodes import *
7
+
8
+ class ExpressionParser:
9
+ """Parse mathematical expressions."""
10
+
11
+ def __init__(self):
12
+ self.tokenizer = Tokenizer()
13
+ self.current_token = None
14
+ self.tokens = []
15
+ self.position = 0
16
+
17
+ def parse(self, expression):
18
+ """Parse an expression string into an AST."""
19
+ if not expression or expression.strip() == '':
20
+ raise ParserError("Expression cannot be empty")
21
+
22
+ self.tokens = self.tokenizer.tokenize(expression)
23
+ self.position = 0
24
+
25
+ if not self.tokens:
26
+ raise ParserError("No valid tokens found in expression")
27
+
28
+ self.current_token = self.tokens[0]
29
+ return self.parse_expression()
30
+
31
+ def parse_expression(self):
32
+ """Parse an expression (addition/subtraction)."""
33
+ node = self.parse_term()
34
+
35
+ while self.current_token and self.current_token.type in ('PLUS', 'MINUS'):
36
+ if self.current_token.type == 'PLUS':
37
+ self.advance()
38
+ right = self.parse_term()
39
+ node = BinOpNode('+', node, right)
40
+ elif self.current_token.type == 'MINUS':
41
+ self.advance()
42
+ right = self.parse_term()
43
+ node = BinOpNode('-', node, right)
44
+
45
+ return node
46
+
47
+ def parse_term(self):
48
+ """Parse a term (multiplication/division/floor)."""
49
+ node = self.parse_factor()
50
+
51
+ while self.current_token and self.current_token.type in ('MULTIPLY', 'DIVIDE', 'FLOOR_DIVIDE'):
52
+ if self.current_token.type == 'MULTIPLY':
53
+ self.advance()
54
+ right = self.parse_factor()
55
+ node = BinOpNode('*', node, right)
56
+ elif self.current_token.type == 'DIVIDE':
57
+ self.advance()
58
+ right = self.parse_factor()
59
+ node = BinOpNode('/', node, right)
60
+ elif self.current_token.type == 'FLOOR_DIVIDE':
61
+ self.advance()
62
+ right = self.parse_factor()
63
+ node = BinOpNode('//', node, right)
64
+
65
+ return node
66
+
67
+ def parse_factor(self):
68
+ """Parse a factor (number, parenthesized expression, function call)."""
69
+ if not self.current_token:
70
+ raise ParserError("Unexpected end of expression")
71
+
72
+ if self.current_token.type == 'NUMBER':
73
+ value = self.current_token.value
74
+ self.advance()
75
+ return NumberNode(value)
76
+
77
+ elif self.current_token.type == 'LPAREN':
78
+ self.advance()
79
+ node = self.parse_expression()
80
+ if not self.current_token or self.current_token.type != 'RPAREN':
81
+ raise ParserError("Missing closing parenthesis")
82
+ self.advance()
83
+ return node
84
+
85
+ elif self.current_token.type == 'FUNCTION':
86
+ func_name = self.current_token.value
87
+ self.advance()
88
+ if not self.current_token or self.current_token.type != 'LPAREN':
89
+ raise ParserError(f"Missing parenthesis after function '{func_name}'")
90
+ self.advance()
91
+ args = []
92
+ if self.current_token and self.current_token.type != 'RPAREN':
93
+ args.append(self.parse_expression())
94
+ while self.current_token and self.current_token.type == 'COMMA':
95
+ self.advance()
96
+ args.append(self.parse_expression())
97
+ if not self.current_token or self.current_token.type != 'RPAREN':
98
+ raise ParserError(f"Missing closing parenthesis for function '{func_name}'")
99
+ self.advance()
100
+ return FunctionNode(func_name, args)
101
+
102
+ else:
103
+ raise ParserError(f"Unexpected token: {self.current_token.type}")
104
+
105
+ def advance(self):
106
+ """Advance to the next token."""
107
+ self.position += 1
108
+ if self.position < len(self.tokens):
109
+ self.current_token = self.tokens[self.position]
110
+ else:
111
+ self.current_token = None
@@ -0,0 +1,52 @@
1
+ """Tokenizer for mathematical expressions."""
2
+
3
+ import re
4
+ from .ast_nodes import Token
5
+
6
+ class Tokenizer:
7
+ """Convert expression string into tokens."""
8
+
9
+ def __init__(self):
10
+ self.token_patterns = [
11
+ ('NUMBER', r'\d+\.?\d*'),
12
+ ('FUNCTION', r'(?:sqrt|sin|cos|tan|log|abs|ceil|floor|round)\b'),
13
+ ('PLUS', r'\+'),
14
+ ('MINUS', r'-'),
15
+ ('MULTIPLY', r'\*'),
16
+ ('DIVIDE', r'/'),
17
+ ('FLOOR_DIVIDE', r'//'),
18
+ ('LPAREN', r'\('),
19
+ ('RPAREN', r'\)'),
20
+ ('COMMA', r','),
21
+ ('WHITESPACE', r'\s+'),
22
+ ]
23
+ self.token_regex = re.compile(
24
+ '|'.join(f'(?P<{name}>{pattern})' for name, pattern in self.token_patterns)
25
+ )
26
+
27
+ def tokenize(self, expression):
28
+ """Tokenize an expression string."""
29
+ tokens = []
30
+ position = 0
31
+ line = 1
32
+ col = 1
33
+
34
+ for match in self.token_regex.finditer(expression):
35
+ token_type = match.lastgroup
36
+ value = match.group()
37
+
38
+ if token_type == 'WHITESPACE':
39
+ # Count newlines for position tracking
40
+ lines = value.count('\n')
41
+ if lines > 0:
42
+ line += lines
43
+ col = len(value) - value.rfind('\n') - 1
44
+ else:
45
+ col += len(value)
46
+ continue
47
+
48
+ tokens.append(Token(token_type, value, position, line, col))
49
+ position += len(value)
50
+ col += len(value)
51
+
52
+ return tokens
@@ -0,0 +1 @@
1
+ """Utility modules."""
@@ -0,0 +1,64 @@
1
+ """Output formatting utilities."""
2
+
3
+ import math
4
+
5
+ def format_result(result):
6
+ """Format the result for display."""
7
+ if isinstance(result, float):
8
+ # Handle special values
9
+ if math.isnan(result):
10
+ return "NaN (Not a Number)"
11
+ if math.isinf(result):
12
+ return "Infinity" if result > 0 else "-Infinity"
13
+
14
+ # Remove trailing zeros for cleaner display
15
+ if result.is_integer():
16
+ return str(int(result))
17
+ # Limit decimal places to 10
18
+ return f"{result:.10f}".rstrip('0').rstrip('.')
19
+ return str(result)
20
+
21
+ def format_expression(operation, num1, num2, result):
22
+ """Format a complete mathematical expression."""
23
+ symbols = {
24
+ 'add': '+',
25
+ 'subtract': '-',
26
+ 'multiply': '×',
27
+ 'divide': '÷',
28
+ 'floor': '//'
29
+ }
30
+ symbol = symbols.get(operation, '?')
31
+ return f"{format_result(num1)} {symbol} {format_result(num2)} = {format_result(result)}"
32
+
33
+ def format_table(data, headers=None):
34
+ """Format data as a table."""
35
+ if not data:
36
+ return ""
37
+
38
+ if headers:
39
+ rows = [headers] + data
40
+ else:
41
+ rows = data
42
+
43
+ # Calculate column widths
44
+ col_widths = []
45
+ for col in range(len(rows[0])):
46
+ max_width = max(len(str(row[col])) for row in rows)
47
+ col_widths.append(max_width + 2)
48
+
49
+ # Build table
50
+ result = []
51
+ for i, row in enumerate(rows):
52
+ line = "|"
53
+ for j, cell in enumerate(row):
54
+ line += f" {str(cell).ljust(col_widths[j] - 1)}|"
55
+ result.append(line)
56
+
57
+ # Add header separator
58
+ if i == 0 and headers:
59
+ separator = "+"
60
+ for width in col_widths:
61
+ separator += "-" * width + "+"
62
+ result.insert(1, separator)
63
+
64
+ return "\n".join(result)
@@ -0,0 +1,43 @@
1
+ """Input validation utilities."""
2
+
3
+ import math
4
+ from ..exceptions.calculator_errors import CalculatorError
5
+
6
+ def validate_number(value):
7
+ """Validate and convert a string to a number (int or float)."""
8
+ if not value or value.strip() == '':
9
+ raise CalculatorError("Input cannot be empty")
10
+
11
+ value = value.strip().lower()
12
+
13
+ # Check for special values
14
+ if value in ['inf', 'infinity']:
15
+ return float('inf')
16
+ if value == '-inf':
17
+ return float('-inf')
18
+ if value in ['nan', 'not a number']:
19
+ return float('nan')
20
+
21
+ try:
22
+ # Try int first
23
+ return int(value)
24
+ except ValueError:
25
+ try:
26
+ # Try float
27
+ return float(value)
28
+ except ValueError:
29
+ raise CalculatorError(f"'{value}' is not a valid number")
30
+
31
+ def is_valid_number(value):
32
+ """Check if a value is a valid number (not NaN or Infinity)."""
33
+ if isinstance(value, (int, float)):
34
+ return not (math.isnan(value) or math.isinf(value))
35
+ return False
36
+
37
+ def is_integer(value):
38
+ """Check if a value is an integer."""
39
+ if isinstance(value, int):
40
+ return True
41
+ if isinstance(value, float):
42
+ return value.is_integer()
43
+ return False
@@ -0,0 +1,126 @@
1
+ Metadata-Version: 2.1
2
+ Name: nexuscalc
3
+ Version: 1.0.0
4
+ Summary: A powerful calculator application with an interactive CLI
5
+ Home-page: https://github.com/yourusername/nexuscalc
6
+ Author: NexusCalc Team
7
+ License: UNKNOWN
8
+ Platform: UNKNOWN
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.6
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+
16
+ # NexusCalc
17
+
18
+ A powerful calculator application with an interactive command-line interface.
19
+
20
+ ## Features
21
+
22
+ - ➕ Addition
23
+ - ➖ Subtraction
24
+ - ✖️ Multiplication
25
+ - ➗ Division with zero division error handling
26
+ - 🏠 Floor division
27
+ - 🔢 Mathematical constants (pi, e, tau)
28
+ - 📐 Advanced mathematical functions (sqrt, sin, cos, tan, log)
29
+ - 🛡️ Comprehensive error handling
30
+ - 💎 Clean number formatting
31
+ - 🎨 Colorful and interactive CLI
32
+
33
+ ## Installation
34
+
35
+ ```bash
36
+ python -m pip install nexuscalc
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ ### Interactive Mode
42
+
43
+ ```python
44
+ from nexuscalc import nexuscalc
45
+ nexuscalc.calculate()
46
+ ```
47
+
48
+ Or:
49
+
50
+ ```python
51
+ import nexuscalc
52
+ nexuscalc.nexuscalc.calculate()
53
+ ```
54
+
55
+ Or after installation:
56
+
57
+ ```bash
58
+ nexuscalc
59
+ ```
60
+
61
+ ### Programmatic Usage
62
+
63
+ ```python
64
+ from nexuscalc.core.operations import Operations
65
+
66
+ ops = Operations()
67
+ result = ops.add(5, 3) # Returns 8
68
+ ```
69
+
70
+ ### Advanced Expression Evaluation
71
+
72
+ ```python
73
+ from nexuscalc.evaluator.evaluator import Evaluator
74
+
75
+ eval = Evaluator()
76
+ result = eval.evaluate_expression("2 + 3 * 4") # Returns 14
77
+ ```
78
+
79
+ ## Error Handling
80
+
81
+ NexusCalc includes comprehensive error handling for:
82
+
83
+ - Division by zero (with friendly error messages)
84
+ - Invalid input formats
85
+ - Numeric overflow
86
+ - Expression parsing errors
87
+ - Function evaluation errors
88
+
89
+ ## Development
90
+
91
+ Run tests:
92
+
93
+ ```bash
94
+ python -m pytest tests/ -v
95
+ ```
96
+
97
+ Run tests with coverage:
98
+
99
+ ```bash
100
+ python -m pytest tests/ --cov=src/nexuscalc
101
+ ```
102
+
103
+ ## Project Structure
104
+
105
+ ```
106
+ nexuscalc/
107
+ ├── src/nexuscalc/
108
+ │ ├── core/ # Core calculator logic
109
+ │ ├── parsers/ # Expression parsing
110
+ │ ├── evaluator/ # Expression evaluation
111
+ │ ├── utils/ # Utilities and helpers
112
+ │ └── exceptions/ # Custom exceptions
113
+ ├── tests/ # Unit tests
114
+ ├── examples/ # Usage examples
115
+ └── setup.py # Package setup
116
+ ```
117
+
118
+ ## License
119
+
120
+ MIT
121
+
122
+ ## Author
123
+
124
+ Light Bulb Experiments © 2026
125
+
126
+
@@ -0,0 +1,25 @@
1
+ LICENSE
2
+ README.md
3
+ setup.py
4
+ src/nexuscalc/__init__.py
5
+ src/nexuscalc.egg-info/PKG-INFO
6
+ src/nexuscalc.egg-info/SOURCES.txt
7
+ src/nexuscalc.egg-info/dependency_links.txt
8
+ src/nexuscalc.egg-info/entry_points.txt
9
+ src/nexuscalc.egg-info/top_level.txt
10
+ src/nexuscalc/core/__init__.py
11
+ src/nexuscalc/core/calculator.py
12
+ src/nexuscalc/core/constants.py
13
+ src/nexuscalc/core/operations.py
14
+ src/nexuscalc/evaluator/__init__.py
15
+ src/nexuscalc/evaluator/evaluator.py
16
+ src/nexuscalc/evaluator/functions.py
17
+ src/nexuscalc/exceptions/__init__.py
18
+ src/nexuscalc/exceptions/calculator_errors.py
19
+ src/nexuscalc/parsers/__init__.py
20
+ src/nexuscalc/parsers/ast_nodes.py
21
+ src/nexuscalc/parsers/expression_parser.py
22
+ src/nexuscalc/parsers/tokenizer.py
23
+ src/nexuscalc/utils/__init__.py
24
+ src/nexuscalc/utils/formatters.py
25
+ src/nexuscalc/utils/validators.py
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ nexuscalc = nexuscalc:calculate
3
+
@@ -0,0 +1 @@
1
+ nexuscalc