Skip to content

Commit 711e8f5

Browse files
committed
Pycalc release v1.0.
No module support yet.
1 parent 17385ed commit 711e8f5

File tree

4 files changed

+335
-0
lines changed

4 files changed

+335
-0
lines changed

final_task/__init__.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import argparse
2+
from pycalc_class import PyCalc
3+
4+
5+
def main():
6+
parser = argparse.ArgumentParser()
7+
parser.add_argument("EXPRESSION", help="expression string to evaluate")
8+
args = parser.parse_args()
9+
calc = PyCalc()
10+
result = calc.calculate(args.EXPRESSION)
11+
print(result)
12+
13+
14+
if __name__ == '__main__':
15+
main()

final_task/operators.pkl

Whitespace-only changes.

final_task/pycalc_class.py

Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
import math
2+
import re
3+
import types
4+
import pickle
5+
6+
from collections import namedtuple
7+
8+
9+
class PyCalc:
10+
11+
def __init__(self):
12+
13+
full_info = namedtuple("full_info", ("func", "priority", "number_args", "regex", "tag"))
14+
regex_and_tag = namedtuple("regex_and_tag", ("regex", "tag"))
15+
16+
cfg_name = "operators.pkl"
17+
18+
self.tag_advanced = 'advanced'
19+
self.tag_constant = 'constant'
20+
self.tag_common = 'common'
21+
self.tag_number = 'number'
22+
23+
try:
24+
25+
with open(cfg_name, 'rb') as pickle_file:
26+
obj = pickle.load(pickle_file)
27+
self.constants, self.operators, self.token_exprs = obj
28+
29+
except Exception:
30+
31+
math_priority = 8
32+
33+
common_operators = {
34+
35+
",": full_info(None, 9, None, r',', self.tag_common),
36+
37+
38+
"abs": full_info(float.__abs__, 8, 1, r'abs', self.tag_common),
39+
"round": full_info(float.__round__, 8, 1, r'round', self.tag_common),
40+
41+
"$": full_info(float.__pos__, 7, 1, r'\$', self.tag_common),
42+
"#": full_info(float.__neg__, 7, 1, r'\#', self.tag_common),
43+
44+
"^": full_info(float.__pow__, 6, 2, r'\^', self.tag_common),
45+
46+
"*": full_info(float.__mul__, 5, 2, r'\*', self.tag_common),
47+
"/": full_info(float.__truediv__, 5, 2, r'/', self.tag_common),
48+
49+
"%": full_info(float.__mod__, 4, 2, r'%', self.tag_common),
50+
"//": full_info(float.__floordiv__, 4, 2, r'//', self.tag_common),
51+
52+
"-": full_info(float.__sub__, 2, 2, r'-', self.tag_common),
53+
"+": full_info(float.__add__, 2, 2, r'\+', self.tag_common),
54+
55+
56+
57+
"(": full_info(None, 1, 0, r'\(', self.tag_common),
58+
59+
"<=": full_info(float.__le__, 0, 2, r'<=', self.tag_common),
60+
">=": full_info(float.__ge__, 0, 2, r'>=', self.tag_common),
61+
"==": full_info(float.__eq__, 0, 2, r'==', self.tag_common),
62+
"!=": full_info(float.__ne__, 0, 2, r'!=', self.tag_common),
63+
"<": full_info(float.__lt__, 0, 2, r'<', self.tag_common),
64+
">": full_info(float.__gt__, 0, 2, r'>', self.tag_common),
65+
66+
")": full_info(None, None, None, r'\)', self.tag_common),
67+
68+
"space": full_info(None, None, None, r'[ \n\t]+', None),
69+
"int_n": full_info(None, None, None, r'[0-9]+', self.tag_number),
70+
"int_f": full_info(None, None, None, r'[0-9]+\.[0-9]', self.tag_number),
71+
"int_f2": full_info(None, None, None, r'\.[0-9]', self.tag_number),
72+
73+
74+
}
75+
76+
math_operators, math_constants = PyCalc.get_math_operators(math_priority=math_priority,
77+
tag_operators=self.tag_advanced,
78+
tag_constants=self.tag_constant,
79+
tuple_template = full_info
80+
)
81+
82+
self.operators = common_operators
83+
self.operators.update(math_operators)
84+
self.constants = math_constants
85+
86+
token_expressions = []
87+
for item in self.operators.values():
88+
token_expressions.append(regex_and_tag(item.regex, item.tag))
89+
for item in self.constants.values():
90+
token_expressions.append(regex_and_tag(item.regex, item.tag))
91+
92+
token_expressions.sort(reverse=True)
93+
self.token_exprs = token_expressions
94+
95+
try:
96+
obj = [self.constants,
97+
self.operators,
98+
self.token_exprs]
99+
with open(cfg_name, 'wb') as pickle_file:
100+
pickle.dump(obj, pickle_file)
101+
except Exception:
102+
pass
103+
104+
def stack_from_string(self, input_string):
105+
106+
pattern = r"[0-9][ \n\t]+[0-9]"
107+
if re.search(pattern, input_string):
108+
raise RuntimeError("ERROR: Unknown syntax!")
109+
110+
patterns_and_replacements = [
111+
(r"--", r"+"),
112+
(r"\++\+", r"+"),
113+
(r"\+-", r"-"),
114+
(r"-\+", r"-"),
115+
(r"\)\(", r")*(")
116+
]
117+
118+
break_bool = True
119+
while break_bool:
120+
break_bool = False
121+
for item in patterns_and_replacements:
122+
input_string = re.sub(item[0], item[1], input_string)
123+
for item in patterns_and_replacements:
124+
if re.search(item[0], input_string):
125+
break_bool = True
126+
break
127+
128+
str_and_tag = namedtuple("str_and_tag", ("s", "tag"))
129+
string_as_stack = PyCalc.lexer(input_string, self.token_exprs, str_and_tag)
130+
131+
temporary_stack = ["$"]
132+
prev_item = str_and_tag("$", self.tag_common)
133+
bracket_balance = 0
134+
135+
for index, item in enumerate(string_as_stack):
136+
137+
if item.s == "(":
138+
bracket_balance += 1
139+
elif item.s == ")":
140+
bracket_balance -= 1
141+
if bracket_balance < 0:
142+
raise RuntimeError("ERROR: brackets aren't balanced!")
143+
144+
if ((item.tag == self.tag_constant or item.tag == self.tag_advanced or item.tag == self.tag_number) and
145+
(prev_item.s == ")" or prev_item.tag == self.tag_constant or prev_item.tag == self.tag_number)) or \
146+
((prev_item.tag == self.tag_constant or prev_item.tag == self.tag_number) and item.s == "("):
147+
temporary_stack.append("*")
148+
149+
if prev_item.tag == self.tag_common and prev_item.s != ")":
150+
if item.s == "+":
151+
continue
152+
elif item.s == "-":
153+
temporary_stack.append("#")
154+
continue
155+
156+
temporary_stack.append(item.s)
157+
prev_item = item
158+
else:
159+
string_as_stack = temporary_stack[1:]
160+
if bracket_balance != 0:
161+
raise RuntimeError("ERROR: brackets aren't balanced!")
162+
163+
return string_as_stack
164+
165+
def rpn_from_stacked_string(self, stack):
166+
167+
temporary_stack = []
168+
rpn_stack = []
169+
170+
for item in stack:
171+
172+
if item not in self.operators:
173+
rpn_stack.append(item)
174+
elif temporary_stack:
175+
176+
if item == ")":
177+
temp = temporary_stack.pop()
178+
while temp != "(":
179+
rpn_stack.append(temp)
180+
temp = temporary_stack.pop()
181+
elif item == "(":
182+
temporary_stack.append(item)
183+
elif item == ",":
184+
while temporary_stack[-1] != "(":
185+
rpn_stack.append(temporary_stack.pop())
186+
elif self.operators[temporary_stack[-1]].priority <= self.operators[item].priority:
187+
temporary_stack.append(item)
188+
else:
189+
temp_priority = self.operators[item].priority
190+
while temporary_stack and self.operators[temporary_stack[-1]].priority >= temp_priority:
191+
rpn_stack.append(temporary_stack.pop())
192+
else:
193+
temporary_stack.append(item)
194+
else:
195+
temporary_stack.append(item)
196+
else:
197+
while temporary_stack:
198+
rpn_stack.append(temporary_stack.pop())
199+
200+
return rpn_stack
201+
202+
def execute_rpn(self, rpn_stack):
203+
204+
temporary_stack = []
205+
206+
for item in rpn_stack:
207+
208+
# print(item+":"+str(temporary_stack))
209+
210+
if item in self.constants:
211+
temporary_stack.append(self.constants[item].func)
212+
continue
213+
elif item in self.operators:
214+
215+
count = self.operators[item].number_args
216+
args = []
217+
for i in range(count):
218+
args.append(float(temporary_stack.pop()))
219+
args.reverse()
220+
result = self.operators[item].func(*args)
221+
222+
temporary_stack.append(result)
223+
else:
224+
temporary_stack.append(item)
225+
226+
result = temporary_stack.pop()
227+
if temporary_stack:
228+
raise RuntimeError("ERROR: Unknown operation!")
229+
230+
return result
231+
232+
def calculate(self, input_string):
233+
234+
# print(input_string)
235+
try:
236+
stacked_string = self.stack_from_string(input_string)
237+
except RuntimeError as rerror:
238+
print(rerror.args[0])
239+
exit(1)
240+
except ValueError as verror:
241+
print(verror.args[0])
242+
exit(1)
243+
# print(stacked_string)
244+
stacked_rpn = self.rpn_from_stacked_string(stacked_string)
245+
# print(stacked_rpn)
246+
try:
247+
result = self.execute_rpn(stacked_rpn)
248+
except IndexError:
249+
print("ERROR: Wrong operations order!")
250+
exit(1)
251+
except ZeroDivisionError:
252+
print("ERROR: Division by zero!")
253+
exit(1)
254+
except RuntimeError as rerror:
255+
print(rerror.args[0])
256+
exit(1)
257+
return result
258+
259+
@staticmethod
260+
def get_math_operators(math_priority, tag_operators, tag_constants, tuple_template):
261+
"""
262+
Returns dictionary:
263+
{"operation_name": (math.operation_name, self.number_of_operation's arguments, operation's value)}
264+
"""
265+
266+
pattern = r"\(.*\)"
267+
coma_pattern = r"\,"
268+
269+
math_operators = {}
270+
math_constants = {}
271+
272+
for item in dir(math):
273+
if isinstance(math.__dict__.get(item), types.BuiltinFunctionType):
274+
275+
if item.find("__"):
276+
res = re.search(pattern, math.__dict__.get(item).__doc__)
277+
res = re.findall(coma_pattern, res.group())
278+
math_operators.update({item: tuple_template(math.__dict__.get(item), math_priority, len(res) + 1,
279+
item, tag_operators)})
280+
281+
else:
282+
if item.find("__"):
283+
math_constants.update({item: tuple_template(math.__dict__.get(item), None, None, item,
284+
tag_constants)})
285+
286+
return math_operators, math_constants
287+
288+
@staticmethod
289+
def lexer(characters, token_exprs, tuple_template):
290+
pos = 0
291+
tokens = []
292+
while pos < len(characters):
293+
match = None
294+
for token_expr in token_exprs:
295+
pattern, tag = token_expr
296+
regex = re.compile(pattern)
297+
match = regex.match(characters, pos)
298+
if match:
299+
text = match.group(0)
300+
if tag:
301+
token = tuple_template(text, tag)
302+
tokens.append(token)
303+
break
304+
if not match:
305+
raise RuntimeError('Illegal character: %s\n' % characters[pos])
306+
else:
307+
pos = match.end(0)
308+
return tokens

final_task/setup.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from setuptools import setup
2+
3+
setup(
4+
name='pycalc',
5+
version='1.0',
6+
packages=[''],
7+
url='',
8+
license='Free',
9+
author='Gleb Nikitin',
10+
author_email='[email protected]',
11+
description='Python calculator.'
12+
)

0 commit comments

Comments
 (0)