Skip to content

Commit 031d0e6

Browse files
committed
Added command line calculator script
1 parent 626c456 commit 031d0e6

File tree

1 file changed

+175
-0
lines changed

1 file changed

+175
-0
lines changed

scripts/command_line_calculator.py

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
"""
2+
Expression Evaluator
3+
--------------------
4+
A Python module that evaluates arithmetic and bitwise expressions.
5+
6+
Supports:
7+
+ - * / ** << >> & | ^ ( )
8+
9+
10+
11+
Usage:
12+
>>> from evaluator import evaluate
13+
>>> evaluate("2 + 3 * 4")
14+
14
15+
>>> evaluate("(2 + 3) * 4")
16+
20
17+
>>> evaluate("2 ** 8")
18+
256
19+
>>> evaluate("10 | 6")
20+
14
21+
>>> evaluate("10 & 6")
22+
2
23+
>>> evaluate("(10 | 6) ^ 5")
24+
9
25+
>>> evaluate("10 / 4")
26+
2.5
27+
"""
28+
29+
import re
30+
31+
def evaluate(expr: str):
32+
"""
33+
Evaluate an arithmetic/bitwise expression string and return result.
34+
35+
Args:
36+
expr (str): Expression to evaluate
37+
38+
Returns:
39+
int | float: The computed result, automatically casted.
40+
41+
Examples:
42+
>>> evaluate("2 + 3 * 4")
43+
14
44+
>>> evaluate("2 ** 10")
45+
1024
46+
>>> evaluate("10 / 4")
47+
2.5
48+
>>> evaluate("(10 | 6) ^ 5")
49+
9
50+
"""
51+
52+
# --- Tokenization ---
53+
tokens = re.findall(r'\d+\.\d+|\d+|\*\*|<<|>>|[&|^+\-*/()]', expr.replace(" ", ""))
54+
55+
# --- Handle unary minus (e.g., -5 + 3) and minus in starting ---
56+
if tokens and tokens[0] == "-":
57+
tokens.insert(0, "0")
58+
59+
def clean(lst):
60+
return [x for x in lst if x is not None]
61+
62+
def solve_power(z):
63+
while "**" in z:
64+
i = z.index("**")
65+
z[i - 1] = float(z[i - 1]) ** float(z[i + 1])
66+
z[i:i + 2] = [None, None]
67+
z = clean(z)
68+
return z
69+
70+
def solve_div(z):
71+
while "/" in z:
72+
i = z.index("/")
73+
z[i - 1] = float(z[i - 1]) / float(z[i + 1])
74+
z[i:i + 2] = [None, None]
75+
z = clean(z)
76+
return z
77+
78+
def solve_mul(z):
79+
while "*" in z:
80+
i = z.index("*")
81+
z[i - 1] = float(z[i - 1]) * float(z[i + 1])
82+
z[i:i + 2] = [None, None]
83+
z = clean(z)
84+
return z
85+
86+
def solve_minus(z):
87+
while "-" in z:
88+
i = z.index("-")
89+
z[i] = "+"
90+
z[i + 1] = -1 * float(z[i + 1])
91+
return z
92+
93+
def solve_add(z):
94+
while "+" in z:
95+
i = z.index("+")
96+
z[i - 1] = float(z[i - 1]) + float(z[i + 1])
97+
z[i:i + 2] = [None, None]
98+
z = clean(z)
99+
return z
100+
101+
def solve_lshift(z):
102+
while "<<" in z:
103+
i = z.index("<<")
104+
z[i - 1] = int(float(z[i - 1])) << int(float(z[i + 1]))
105+
z[i:i + 2] = [None, None]
106+
z = clean(z)
107+
return z
108+
109+
def solve_rshift(z):
110+
while ">>" in z:
111+
i = z.index(">>")
112+
z[i - 1] = int(float(z[i - 1])) >> int(float(z[i + 1]))
113+
z[i:i + 2] = [None, None]
114+
z = clean(z)
115+
return z
116+
117+
def solve_and(z):
118+
while "&" in z:
119+
i = z.index("&")
120+
z[i - 1] = int(float(z[i - 1])) & int(float(z[i + 1]))
121+
z[i:i + 2] = [None, None]
122+
z = clean(z)
123+
return z
124+
125+
def solve_xor(z):
126+
while "^" in z:
127+
i = z.index("^")
128+
z[i - 1] = int(float(z[i - 1])) ^ int(float(z[i + 1]))
129+
z[i:i + 2] = [None, None]
130+
z = clean(z)
131+
return z
132+
133+
def solve_or(z):
134+
while "|" in z:
135+
i = z.index("|")
136+
z[i - 1] = int(float(z[i - 1])) | int(float(z[i + 1]))
137+
z[i:i + 2] = [None, None]
138+
z = clean(z)
139+
return z
140+
141+
def solve_parentheses(z):
142+
while "(" in z:
143+
close = z.index(")")
144+
open_ = max(i for i, v in enumerate(z[:close]) if v == "(")
145+
inner = z[open_ + 1:close]
146+
result = evaluate(" ".join(map(str, inner)))
147+
z = z[:open_] + [str(result)] + z[close + 1:]
148+
return z
149+
150+
tokens = solve_parentheses(tokens)
151+
152+
# Precedence order
153+
for func in [solve_power, solve_div, solve_mul, solve_minus, solve_add,
154+
solve_lshift, solve_rshift, solve_and, solve_xor, solve_or]:
155+
tokens = func(tokens)
156+
157+
result = tokens[0]
158+
159+
# Return as int if it’s cleanly integer-valued
160+
if isinstance(result, (int, float)):
161+
if abs(result - int(result)) < 1e-9:
162+
return int(result)
163+
return float(result)
164+
165+
# If it's a string (edge-case), convert safely
166+
try:
167+
f = float(result)
168+
return int(f) if f.is_integer() else f
169+
except ValueError:
170+
raise ValueError("Invalid expression or unsupported syntax.")
171+
172+
173+
if __name__ == "__main__":
174+
import doctest
175+
doctest.testmod(verbose=True)

0 commit comments

Comments
 (0)