From 711e8f5130e82ceb96af3cd244613c9b95d46d59 Mon Sep 17 00:00:00 2001 From: Gleb Date: Sat, 8 Dec 2018 17:15:27 +0300 Subject: [PATCH 01/18] Pycalc release v1.0. No module support yet. --- final_task/__init__.py | 15 ++ final_task/operators.pkl | 0 final_task/pycalc_class.py | 308 +++++++++++++++++++++++++++++++++++++ final_task/setup.py | 12 ++ 4 files changed, 335 insertions(+) create mode 100644 final_task/operators.pkl create mode 100644 final_task/pycalc_class.py diff --git a/final_task/__init__.py b/final_task/__init__.py index e69de29..a65da3e 100644 --- a/final_task/__init__.py +++ b/final_task/__init__.py @@ -0,0 +1,15 @@ +import argparse +from pycalc_class import PyCalc + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("EXPRESSION", help="expression string to evaluate") + args = parser.parse_args() + calc = PyCalc() + result = calc.calculate(args.EXPRESSION) + print(result) + + +if __name__ == '__main__': + main() diff --git a/final_task/operators.pkl b/final_task/operators.pkl new file mode 100644 index 0000000..e69de29 diff --git a/final_task/pycalc_class.py b/final_task/pycalc_class.py new file mode 100644 index 0000000..48de60e --- /dev/null +++ b/final_task/pycalc_class.py @@ -0,0 +1,308 @@ +import math +import re +import types +import pickle + +from collections import namedtuple + + +class PyCalc: + + def __init__(self): + + full_info = namedtuple("full_info", ("func", "priority", "number_args", "regex", "tag")) + regex_and_tag = namedtuple("regex_and_tag", ("regex", "tag")) + + cfg_name = "operators.pkl" + + self.tag_advanced = 'advanced' + self.tag_constant = 'constant' + self.tag_common = 'common' + self.tag_number = 'number' + + try: + + with open(cfg_name, 'rb') as pickle_file: + obj = pickle.load(pickle_file) + self.constants, self.operators, self.token_exprs = obj + + except Exception: + + math_priority = 8 + + common_operators = { + + ",": full_info(None, 9, None, r',', self.tag_common), + + + "abs": full_info(float.__abs__, 8, 1, r'abs', self.tag_common), + "round": full_info(float.__round__, 8, 1, r'round', self.tag_common), + + "$": full_info(float.__pos__, 7, 1, r'\$', self.tag_common), + "#": full_info(float.__neg__, 7, 1, r'\#', self.tag_common), + + "^": full_info(float.__pow__, 6, 2, r'\^', self.tag_common), + + "*": full_info(float.__mul__, 5, 2, r'\*', self.tag_common), + "/": full_info(float.__truediv__, 5, 2, r'/', self.tag_common), + + "%": full_info(float.__mod__, 4, 2, r'%', self.tag_common), + "//": full_info(float.__floordiv__, 4, 2, r'//', self.tag_common), + + "-": full_info(float.__sub__, 2, 2, r'-', self.tag_common), + "+": full_info(float.__add__, 2, 2, r'\+', self.tag_common), + + + + "(": full_info(None, 1, 0, r'\(', self.tag_common), + + "<=": full_info(float.__le__, 0, 2, r'<=', self.tag_common), + ">=": full_info(float.__ge__, 0, 2, r'>=', self.tag_common), + "==": full_info(float.__eq__, 0, 2, r'==', self.tag_common), + "!=": full_info(float.__ne__, 0, 2, r'!=', self.tag_common), + "<": full_info(float.__lt__, 0, 2, r'<', self.tag_common), + ">": full_info(float.__gt__, 0, 2, r'>', self.tag_common), + + ")": full_info(None, None, None, r'\)', self.tag_common), + + "space": full_info(None, None, None, r'[ \n\t]+', None), + "int_n": full_info(None, None, None, r'[0-9]+', self.tag_number), + "int_f": full_info(None, None, None, r'[0-9]+\.[0-9]', self.tag_number), + "int_f2": full_info(None, None, None, r'\.[0-9]', self.tag_number), + + + } + + math_operators, math_constants = PyCalc.get_math_operators(math_priority=math_priority, + tag_operators=self.tag_advanced, + tag_constants=self.tag_constant, + tuple_template = full_info + ) + + self.operators = common_operators + self.operators.update(math_operators) + self.constants = math_constants + + token_expressions = [] + for item in self.operators.values(): + token_expressions.append(regex_and_tag(item.regex, item.tag)) + for item in self.constants.values(): + token_expressions.append(regex_and_tag(item.regex, item.tag)) + + token_expressions.sort(reverse=True) + self.token_exprs = token_expressions + + try: + obj = [self.constants, + self.operators, + self.token_exprs] + with open(cfg_name, 'wb') as pickle_file: + pickle.dump(obj, pickle_file) + except Exception: + pass + + def stack_from_string(self, input_string): + + pattern = r"[0-9][ \n\t]+[0-9]" + if re.search(pattern, input_string): + raise RuntimeError("ERROR: Unknown syntax!") + + patterns_and_replacements = [ + (r"--", r"+"), + (r"\++\+", r"+"), + (r"\+-", r"-"), + (r"-\+", r"-"), + (r"\)\(", r")*(") + ] + + break_bool = True + while break_bool: + break_bool = False + for item in patterns_and_replacements: + input_string = re.sub(item[0], item[1], input_string) + for item in patterns_and_replacements: + if re.search(item[0], input_string): + break_bool = True + break + + str_and_tag = namedtuple("str_and_tag", ("s", "tag")) + string_as_stack = PyCalc.lexer(input_string, self.token_exprs, str_and_tag) + + temporary_stack = ["$"] + prev_item = str_and_tag("$", self.tag_common) + bracket_balance = 0 + + for index, item in enumerate(string_as_stack): + + if item.s == "(": + bracket_balance += 1 + elif item.s == ")": + bracket_balance -= 1 + if bracket_balance < 0: + raise RuntimeError("ERROR: brackets aren't balanced!") + + if ((item.tag == self.tag_constant or item.tag == self.tag_advanced or item.tag == self.tag_number) and + (prev_item.s == ")" or prev_item.tag == self.tag_constant or prev_item.tag == self.tag_number)) or \ + ((prev_item.tag == self.tag_constant or prev_item.tag == self.tag_number) and item.s == "("): + temporary_stack.append("*") + + if prev_item.tag == self.tag_common and prev_item.s != ")": + if item.s == "+": + continue + elif item.s == "-": + temporary_stack.append("#") + continue + + temporary_stack.append(item.s) + prev_item = item + else: + string_as_stack = temporary_stack[1:] + if bracket_balance != 0: + raise RuntimeError("ERROR: brackets aren't balanced!") + + return string_as_stack + + def rpn_from_stacked_string(self, stack): + + temporary_stack = [] + rpn_stack = [] + + for item in stack: + + if item not in self.operators: + rpn_stack.append(item) + elif temporary_stack: + + if item == ")": + temp = temporary_stack.pop() + while temp != "(": + rpn_stack.append(temp) + temp = temporary_stack.pop() + elif item == "(": + temporary_stack.append(item) + elif item == ",": + while temporary_stack[-1] != "(": + rpn_stack.append(temporary_stack.pop()) + elif self.operators[temporary_stack[-1]].priority <= self.operators[item].priority: + temporary_stack.append(item) + else: + temp_priority = self.operators[item].priority + while temporary_stack and self.operators[temporary_stack[-1]].priority >= temp_priority: + rpn_stack.append(temporary_stack.pop()) + else: + temporary_stack.append(item) + else: + temporary_stack.append(item) + else: + while temporary_stack: + rpn_stack.append(temporary_stack.pop()) + + return rpn_stack + + def execute_rpn(self, rpn_stack): + + temporary_stack = [] + + for item in rpn_stack: + + # print(item+":"+str(temporary_stack)) + + if item in self.constants: + temporary_stack.append(self.constants[item].func) + continue + elif item in self.operators: + + count = self.operators[item].number_args + args = [] + for i in range(count): + args.append(float(temporary_stack.pop())) + args.reverse() + result = self.operators[item].func(*args) + + temporary_stack.append(result) + else: + temporary_stack.append(item) + + result = temporary_stack.pop() + if temporary_stack: + raise RuntimeError("ERROR: Unknown operation!") + + return result + + def calculate(self, input_string): + + # print(input_string) + try: + stacked_string = self.stack_from_string(input_string) + except RuntimeError as rerror: + print(rerror.args[0]) + exit(1) + except ValueError as verror: + print(verror.args[0]) + exit(1) + # print(stacked_string) + stacked_rpn = self.rpn_from_stacked_string(stacked_string) + # print(stacked_rpn) + try: + result = self.execute_rpn(stacked_rpn) + except IndexError: + print("ERROR: Wrong operations order!") + exit(1) + except ZeroDivisionError: + print("ERROR: Division by zero!") + exit(1) + except RuntimeError as rerror: + print(rerror.args[0]) + exit(1) + return result + + @staticmethod + def get_math_operators(math_priority, tag_operators, tag_constants, tuple_template): + """ + Returns dictionary: + {"operation_name": (math.operation_name, self.number_of_operation's arguments, operation's value)} + """ + + pattern = r"\(.*\)" + coma_pattern = r"\," + + math_operators = {} + math_constants = {} + + for item in dir(math): + if isinstance(math.__dict__.get(item), types.BuiltinFunctionType): + + if item.find("__"): + res = re.search(pattern, math.__dict__.get(item).__doc__) + res = re.findall(coma_pattern, res.group()) + math_operators.update({item: tuple_template(math.__dict__.get(item), math_priority, len(res) + 1, + item, tag_operators)}) + + else: + if item.find("__"): + math_constants.update({item: tuple_template(math.__dict__.get(item), None, None, item, + tag_constants)}) + + return math_operators, math_constants + + @staticmethod + def lexer(characters, token_exprs, tuple_template): + pos = 0 + tokens = [] + while pos < len(characters): + match = None + for token_expr in token_exprs: + pattern, tag = token_expr + regex = re.compile(pattern) + match = regex.match(characters, pos) + if match: + text = match.group(0) + if tag: + token = tuple_template(text, tag) + tokens.append(token) + break + if not match: + raise RuntimeError('Illegal character: %s\n' % characters[pos]) + else: + pos = match.end(0) + return tokens diff --git a/final_task/setup.py b/final_task/setup.py index e69de29..eba5136 100644 --- a/final_task/setup.py +++ b/final_task/setup.py @@ -0,0 +1,12 @@ +from setuptools import setup + +setup( + name='pycalc', + version='1.0', + packages=[''], + url='', + license='Free', + author='Gleb Nikitin', + author_email='nikitin_gleb@tut.by', + description='Python calculator.' +) From 0c7c9f3624472d91695cc4d3f8876601ddd931e9 Mon Sep 17 00:00:00 2001 From: Gleb Date: Sat, 8 Dec 2018 17:29:11 +0300 Subject: [PATCH 02/18] Pycalc release v1.0.1. --- final_task/{pycalc_class.py => pycalc.py} | 0 final_task/setup.py | 8 ++++---- 2 files changed, 4 insertions(+), 4 deletions(-) rename final_task/{pycalc_class.py => pycalc.py} (100%) diff --git a/final_task/pycalc_class.py b/final_task/pycalc.py similarity index 100% rename from final_task/pycalc_class.py rename to final_task/pycalc.py diff --git a/final_task/setup.py b/final_task/setup.py index eba5136..95cf452 100644 --- a/final_task/setup.py +++ b/final_task/setup.py @@ -1,12 +1,12 @@ -from setuptools import setup +from distutils.core import setup setup( name='pycalc', - version='1.0', - packages=[''], + version='1.0.1', + packages=['pycalc'], url='', license='Free', author='Gleb Nikitin', author_email='nikitin_gleb@tut.by', - description='Python calculator.' + description='Pycalc calculator.' ) From 65872e427282b3cd6a4fad28ea035bca4bbd6ce0 Mon Sep 17 00:00:00 2001 From: Gleb Date: Sat, 8 Dec 2018 17:39:46 +0300 Subject: [PATCH 03/18] Pycalc release v1.0.2. --- final_task/pycalc.py | 2 +- final_task/setup.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/final_task/pycalc.py b/final_task/pycalc.py index 48de60e..6ec6022 100644 --- a/final_task/pycalc.py +++ b/final_task/pycalc.py @@ -76,7 +76,7 @@ def __init__(self): math_operators, math_constants = PyCalc.get_math_operators(math_priority=math_priority, tag_operators=self.tag_advanced, tag_constants=self.tag_constant, - tuple_template = full_info + tuple_template=full_info ) self.operators = common_operators diff --git a/final_task/setup.py b/final_task/setup.py index 95cf452..b99bc22 100644 --- a/final_task/setup.py +++ b/final_task/setup.py @@ -1,12 +1,12 @@ from distutils.core import setup setup( - name='pycalc', - version='1.0.1', - packages=['pycalc'], + name='final_task', + version='1.0.2.', + packages=[''], url='', license='Free', author='Gleb Nikitin', author_email='nikitin_gleb@tut.by', - description='Pycalc calculator.' + description='Python calculator.' ) From b20340d6102717c96f0bacec6f25a475ff1fb8f0 Mon Sep 17 00:00:00 2001 From: Gleb Date: Sat, 8 Dec 2018 17:42:03 +0300 Subject: [PATCH 04/18] Pycalc release v1.0.3. --- final_task/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/final_task/__init__.py b/final_task/__init__.py index a65da3e..40906a2 100644 --- a/final_task/__init__.py +++ b/final_task/__init__.py @@ -1,5 +1,5 @@ import argparse -from pycalc_class import PyCalc +from pycalc import PyCalc def main(): From 66a07c71bb1cfede62565e0a146abf5362bdaf52 Mon Sep 17 00:00:00 2001 From: Gleb Date: Sat, 8 Dec 2018 22:49:29 +0300 Subject: [PATCH 05/18] Pycalc release v1.0.4. --- final_task/__init__.py | 15 --------------- final_task/pycalc.py | 15 ++++++++++++++- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/final_task/__init__.py b/final_task/__init__.py index 40906a2..e69de29 100644 --- a/final_task/__init__.py +++ b/final_task/__init__.py @@ -1,15 +0,0 @@ -import argparse -from pycalc import PyCalc - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument("EXPRESSION", help="expression string to evaluate") - args = parser.parse_args() - calc = PyCalc() - result = calc.calculate(args.EXPRESSION) - print(result) - - -if __name__ == '__main__': - main() diff --git a/final_task/pycalc.py b/final_task/pycalc.py index 6ec6022..c47e4b0 100644 --- a/final_task/pycalc.py +++ b/final_task/pycalc.py @@ -2,7 +2,7 @@ import re import types import pickle - +import argparse from collections import namedtuple @@ -306,3 +306,16 @@ def lexer(characters, token_exprs, tuple_template): else: pos = match.end(0) return tokens + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("EXPRESSION", help="expression string to evaluate") + args = parser.parse_args() + calc = PyCalc() + result = calc.calculate(args.EXPRESSION) + print(result) + + +if __name__ == '__main__': + main() From 2c56e365a70e351c11935d64a183b3aab8c6d28c Mon Sep 17 00:00:00 2001 From: Gleb Date: Sat, 8 Dec 2018 22:56:04 +0300 Subject: [PATCH 06/18] Pycalc release v1.0.5. --- final_task/{pycalc.py => pycalc/__main__.py} | 0 final_task/{ => pycalc}/operators.pkl | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename final_task/{pycalc.py => pycalc/__main__.py} (100%) rename final_task/{ => pycalc}/operators.pkl (100%) diff --git a/final_task/pycalc.py b/final_task/pycalc/__main__.py similarity index 100% rename from final_task/pycalc.py rename to final_task/pycalc/__main__.py diff --git a/final_task/operators.pkl b/final_task/pycalc/operators.pkl similarity index 100% rename from final_task/operators.pkl rename to final_task/pycalc/operators.pkl From 8de65a4ffd827da0b4c3d6c6e377dddebf88e03e Mon Sep 17 00:00:00 2001 From: Gleb Date: Sat, 8 Dec 2018 23:06:23 +0300 Subject: [PATCH 07/18] Pycalc release v1.0.6. --- final_task/{pycalc => }/operators.pkl | 0 final_task/{pycalc/__main__.py => pycalc} | 0 final_task/setup.py | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) rename final_task/{pycalc => }/operators.pkl (100%) rename final_task/{pycalc/__main__.py => pycalc} (100%) diff --git a/final_task/pycalc/operators.pkl b/final_task/operators.pkl similarity index 100% rename from final_task/pycalc/operators.pkl rename to final_task/operators.pkl diff --git a/final_task/pycalc/__main__.py b/final_task/pycalc similarity index 100% rename from final_task/pycalc/__main__.py rename to final_task/pycalc diff --git a/final_task/setup.py b/final_task/setup.py index b99bc22..db1c883 100644 --- a/final_task/setup.py +++ b/final_task/setup.py @@ -1,7 +1,7 @@ from distutils.core import setup setup( - name='final_task', + name='pycalc', version='1.0.2.', packages=[''], url='', From 436071c3671e5b125ebd42aad6e59518faf88f90 Mon Sep 17 00:00:00 2001 From: Gleb Date: Sat, 8 Dec 2018 23:21:40 +0300 Subject: [PATCH 08/18] Pycalc release v1.0.7. --- final_task/{operators.pkl => pycalc/__init__.py} | 0 final_task/pycalc/operators.pkl | 0 final_task/{pycalc => pycalc/pycalc.py} | 0 final_task/setup.py | 12 +++++------- 4 files changed, 5 insertions(+), 7 deletions(-) rename final_task/{operators.pkl => pycalc/__init__.py} (100%) create mode 100644 final_task/pycalc/operators.pkl rename final_task/{pycalc => pycalc/pycalc.py} (100%) diff --git a/final_task/operators.pkl b/final_task/pycalc/__init__.py similarity index 100% rename from final_task/operators.pkl rename to final_task/pycalc/__init__.py diff --git a/final_task/pycalc/operators.pkl b/final_task/pycalc/operators.pkl new file mode 100644 index 0000000..e69de29 diff --git a/final_task/pycalc b/final_task/pycalc/pycalc.py similarity index 100% rename from final_task/pycalc rename to final_task/pycalc/pycalc.py diff --git a/final_task/setup.py b/final_task/setup.py index db1c883..7d9e34e 100644 --- a/final_task/setup.py +++ b/final_task/setup.py @@ -1,12 +1,10 @@ -from distutils.core import setup +from setuptools import setup, find_packages setup( name='pycalc', - version='1.0.2.', - packages=[''], - url='', - license='Free', + version='1.0.0', author='Gleb Nikitin', - author_email='nikitin_gleb@tut.by', - description='Python calculator.' + author_email='nikitin_gleb@tut.byu', + packages=find_packages(), + entry_points={'console_scripts': ['pycalc=pycalc.pycalc:main']}, ) From b86e3f948be9cf568863efca11ed1069b5f2343b Mon Sep 17 00:00:00 2001 From: Gleb Date: Sun, 9 Dec 2018 16:01:14 +0300 Subject: [PATCH 09/18] Pycalc release v1.0.8. --- final_task/pycalc/pycalc.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/final_task/pycalc/pycalc.py b/final_task/pycalc/pycalc.py index c47e4b0..e9c7840 100644 --- a/final_task/pycalc/pycalc.py +++ b/final_task/pycalc/pycalc.py @@ -67,7 +67,7 @@ def __init__(self): "space": full_info(None, None, None, r'[ \n\t]+', None), "int_n": full_info(None, None, None, r'[0-9]+', self.tag_number), - "int_f": full_info(None, None, None, r'[0-9]+\.[0-9]', self.tag_number), + "int_f": full_info(None, None, None, r'[0-9]+\.[0-9]+', self.tag_number), "int_f2": full_info(None, None, None, r'\.[0-9]', self.tag_number), @@ -183,7 +183,7 @@ def rpn_from_stacked_string(self, stack): elif item == ",": while temporary_stack[-1] != "(": rpn_stack.append(temporary_stack.pop()) - elif self.operators[temporary_stack[-1]].priority <= self.operators[item].priority: + elif self.operators[temporary_stack[-1]].priority < self.operators[item].priority: temporary_stack.append(item) else: temp_priority = self.operators[item].priority @@ -254,6 +254,9 @@ def calculate(self, input_string): except RuntimeError as rerror: print(rerror.args[0]) exit(1) + except ValueError as verror: + print(verror.args[0]) + exit(1) return result @staticmethod @@ -283,6 +286,8 @@ def get_math_operators(math_priority, tag_operators, tag_constants, tuple_templa math_constants.update({item: tuple_template(math.__dict__.get(item), None, None, item, tag_constants)}) + math_operators.update({'log': tuple_template(math.log, math_priority, 1, 'log', tag_operators)}) + return math_operators, math_constants @staticmethod @@ -302,7 +307,7 @@ def lexer(characters, token_exprs, tuple_template): tokens.append(token) break if not match: - raise RuntimeError('Illegal character: %s\n' % characters[pos]) + raise RuntimeError('ERROR: Illegal character: %s\n' % characters[pos]) else: pos = match.end(0) return tokens @@ -319,3 +324,7 @@ def main(): if __name__ == '__main__': main() + +# calc = PyCalc() +# result = calc.calculate('6 < = 6') +# print(result) \ No newline at end of file From ff45c99c7734e807bfa475cf8c5d9570cddb69bc Mon Sep 17 00:00:00 2001 From: Gleb Date: Sun, 9 Dec 2018 17:15:52 +0300 Subject: [PATCH 10/18] Pycalc release v1.0.9. --- final_task/pycalc/pycalc.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/final_task/pycalc/pycalc.py b/final_task/pycalc/pycalc.py index e9c7840..747b95d 100644 --- a/final_task/pycalc/pycalc.py +++ b/final_task/pycalc/pycalc.py @@ -125,6 +125,16 @@ def stack_from_string(self, input_string): break_bool = True break + pattern = r"log\(.+," + tmp = re.search(pattern, input_string) + while tmp: + pos = tmp.start() + first_part = input_string[:pos] + second_part = input_string[pos:] + second_part = re.sub("^log", "log2", second_part) + input_string = first_part + second_part + tmp = re.search(pattern, input_string) + str_and_tag = namedtuple("str_and_tag", ("s", "tag")) string_as_stack = PyCalc.lexer(input_string, self.token_exprs, str_and_tag) @@ -183,7 +193,7 @@ def rpn_from_stacked_string(self, stack): elif item == ",": while temporary_stack[-1] != "(": rpn_stack.append(temporary_stack.pop()) - elif self.operators[temporary_stack[-1]].priority < self.operators[item].priority: + elif self.operators[temporary_stack[-1]].priority <= self.operators[item].priority and item == "^": temporary_stack.append(item) else: temp_priority = self.operators[item].priority @@ -255,7 +265,10 @@ def calculate(self, input_string): print(rerror.args[0]) exit(1) except ValueError as verror: - print(verror.args[0]) + print("ERROR: unknown operand!") + exit(1) + except Exception: + print("ERROR: unknown error!") exit(1) return result @@ -287,6 +300,7 @@ def get_math_operators(math_priority, tag_operators, tag_constants, tuple_templa tag_constants)}) math_operators.update({'log': tuple_template(math.log, math_priority, 1, 'log', tag_operators)}) + math_operators.update({'log2': tuple_template(math.log, math_priority, 2, 'log2', tag_operators)}) return math_operators, math_constants @@ -326,5 +340,6 @@ def main(): main() # calc = PyCalc() -# result = calc.calculate('6 < = 6') +# result = calc.calculate('sin(e^log(e^e^sin(23.0),45.0) + cos(3.0+log10(e^-e)))') +# # result = calc.calculate('5+2*2^2^2^(2-1)*sin(pi/2)') # print(result) \ No newline at end of file From 2b4cec64fd165b8e664719ea8ac6fb8e6a9771e3 Mon Sep 17 00:00:00 2001 From: Gleb Date: Sun, 9 Dec 2018 17:20:14 +0300 Subject: [PATCH 11/18] Pycalc release v.1.1.0. Stable release. --- final_task/pycalc/pycalc.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/final_task/pycalc/pycalc.py b/final_task/pycalc/pycalc.py index 747b95d..9e378ee 100644 --- a/final_task/pycalc/pycalc.py +++ b/final_task/pycalc/pycalc.py @@ -264,7 +264,7 @@ def calculate(self, input_string): except RuntimeError as rerror: print(rerror.args[0]) exit(1) - except ValueError as verror: + except ValueError: print("ERROR: unknown operand!") exit(1) except Exception: @@ -341,5 +341,4 @@ def main(): # calc = PyCalc() # result = calc.calculate('sin(e^log(e^e^sin(23.0),45.0) + cos(3.0+log10(e^-e)))') -# # result = calc.calculate('5+2*2^2^2^(2-1)*sin(pi/2)') -# print(result) \ No newline at end of file +# print(result) From c5d75c89b22ae4899d3efa0c2bcde2e28cb2aa4f Mon Sep 17 00:00:00 2001 From: Gleb Date: Tue, 11 Dec 2018 16:31:13 +0300 Subject: [PATCH 12/18] Pycalc release v1.1.1. Doc string added. --- final_task/pycalc/pycalc.py | 175 ++++++++++++++++++++++++++++-------- 1 file changed, 137 insertions(+), 38 deletions(-) diff --git a/final_task/pycalc/pycalc.py b/final_task/pycalc/pycalc.py index 9e378ee..6442051 100644 --- a/final_task/pycalc/pycalc.py +++ b/final_task/pycalc/pycalc.py @@ -7,8 +7,39 @@ class PyCalc: - + """ + Python calculator class. + Evaluates the passed string. + + List of methods: + __init__ - class initialiser; + tokenizer - creates list of tokens from passed string; + rpn - transforms passed stack into reverse polish notation stack; + execute_rpn - executes passed stack (stack have to be an rpn stack) + calculate - calculates passed string using previous methods; + + @static_method + get_math_operatros - transforms math module in following dictionary: + {'name': namedtuple("full_info", ("func", "priority", "number_args", "regex", "tag")}; + lexer - transforms string into token stack. This method is more common, while tokenizer + has more rules to perform. + """ def __init__(self): + """ + Pycalc class initialiser. + Sets a lot of variables, such as tag variables, dictionaries of math operators, math constants, etc. and saves + them into pickle file. On the next run this file'll be unpacked and loaded, so there'll be no need to recalcula- + te dictionaries of math operators and constants. + + Arguments: + No arguments. + + Returns: + No returns. + + Raises: + No raises. + """ full_info = namedtuple("full_info", ("func", "priority", "number_args", "regex", "tag")) regex_and_tag = namedtuple("regex_and_tag", ("regex", "tag")) @@ -101,11 +132,24 @@ def __init__(self): except Exception: pass - def stack_from_string(self, input_string): + def tokenizer(self, input_string): + """ + Creates list of tokens from passed string. + + Arguments: + input_string - (string) input string with math expression. - pattern = r"[0-9][ \n\t]+[0-9]" + Returns: + tokens - (list) list of tokens from passed string. + + Raises: + RuntimeError - Unknown syntax! - in case of spaces between numbers. + RuntimeError - Brackets aren't balanced! - in case of unbalanced brackets. + """ + + pattern = r"[0-9\.*][ \n\t]+[\.*0-9]" if re.search(pattern, input_string): - raise RuntimeError("ERROR: Unknown syntax!") + raise RuntimeError("Unknown syntax: " + re.search(pattern, input_string).group(0)) patterns_and_replacements = [ (r"--", r"+"), @@ -135,44 +179,56 @@ def stack_from_string(self, input_string): input_string = first_part + second_part tmp = re.search(pattern, input_string) - str_and_tag = namedtuple("str_and_tag", ("s", "tag")) - string_as_stack = PyCalc.lexer(input_string, self.token_exprs, str_and_tag) + token_and_tag = namedtuple("str_and_tag", ("token", "tag")) + token_stack = PyCalc.lexer(input_string, self.token_exprs, token_and_tag) temporary_stack = ["$"] - prev_item = str_and_tag("$", self.tag_common) + prev_item = token_and_tag("$", self.tag_common) bracket_balance = 0 - for index, item in enumerate(string_as_stack): + for index, item in enumerate(token_stack): - if item.s == "(": + if item.token == "(": bracket_balance += 1 - elif item.s == ")": + elif item.token == ")": bracket_balance -= 1 if bracket_balance < 0: - raise RuntimeError("ERROR: brackets aren't balanced!") + raise RuntimeError("Brackets aren't balanced!") if ((item.tag == self.tag_constant or item.tag == self.tag_advanced or item.tag == self.tag_number) and - (prev_item.s == ")" or prev_item.tag == self.tag_constant or prev_item.tag == self.tag_number)) or \ - ((prev_item.tag == self.tag_constant or prev_item.tag == self.tag_number) and item.s == "("): + (prev_item.token == ")" or prev_item.tag == self.tag_constant or prev_item.tag == self.tag_number)) or \ + ((prev_item.tag == self.tag_constant or prev_item.tag == self.tag_number) and item.token == "("): temporary_stack.append("*") - if prev_item.tag == self.tag_common and prev_item.s != ")": - if item.s == "+": + if prev_item.tag == self.tag_common and prev_item.token != ")": + if item.token == "+": continue - elif item.s == "-": + elif item.token == "-": temporary_stack.append("#") continue - temporary_stack.append(item.s) + temporary_stack.append(item.token) prev_item = item else: - string_as_stack = temporary_stack[1:] + token_stack = temporary_stack[1:] if bracket_balance != 0: - raise RuntimeError("ERROR: brackets aren't balanced!") + raise RuntimeError("Brackets aren't balanced!") - return string_as_stack + return token_stack - def rpn_from_stacked_string(self, stack): + def rpn(self, stack): + """ + Transforms list of tokens to its rpn (reverse polish notation) form. + + Arguments: + stack - (list) input stack of tokens. + + Returns: + string_as_stack - (list) list of tokens in rpn form. + + Raises: + No raises. + """ temporary_stack = [] rpn_stack = [] @@ -210,6 +266,21 @@ def rpn_from_stacked_string(self, stack): return rpn_stack def execute_rpn(self, rpn_stack): + """ + Executes passed stack in rpn form and returns the result or raises error. + + Arguments: + rpn_stack - (list) input stack of tokens in rpn form. + + Returns: + result - (float) resulting number. + + Raises: + RuntimeError - Unknown operation! - in case of wrong operation oder. + ZeroDivision - in case of division by zero. + IndexError - in case of wrong operation oder. + ValueError - in case of wrong operand. + """ temporary_stack = [] @@ -235,7 +306,7 @@ def execute_rpn(self, rpn_stack): result = temporary_stack.pop() if temporary_stack: - raise RuntimeError("ERROR: Unknown operation!") + raise RuntimeError("Resulting stack isn't empty. Unknown operation!") return result @@ -243,40 +314,51 @@ def calculate(self, input_string): # print(input_string) try: - stacked_string = self.stack_from_string(input_string) - except RuntimeError as rerror: - print(rerror.args[0]) - exit(1) - except ValueError as verror: - print(verror.args[0]) + stacked_string = self.tokenizer(input_string) + except Exception as eerror: + print("ERROR in tokenizer: " + str(eerror.args[0])) exit(1) # print(stacked_string) - stacked_rpn = self.rpn_from_stacked_string(stacked_string) + stacked_rpn = self.rpn(stacked_string) # print(stacked_rpn) try: result = self.execute_rpn(stacked_rpn) except IndexError: - print("ERROR: Wrong operations order!") + print("ERROR in execute_rpn: Wrong operations order!") exit(1) except ZeroDivisionError: - print("ERROR: Division by zero!") + print("ERROR in execute_rpn: Division by zero!") exit(1) except RuntimeError as rerror: - print(rerror.args[0]) + print("ERROR in execute_rpn:" + str(rerror.args[0])) exit(1) except ValueError: - print("ERROR: unknown operand!") + print("ERROR in execute_rpn: unknown operand!") exit(1) except Exception: - print("ERROR: unknown error!") + print("ERROR in execute_rpn: unknown error!") exit(1) return result @staticmethod def get_math_operators(math_priority, tag_operators, tag_constants, tuple_template): """ - Returns dictionary: - {"operation_name": (math.operation_name, self.number_of_operation's arguments, operation's value)} + Parses math module and returns dictionary with followed structure: + {'name': namedtuple("full_info", ("func", "priority", "number_args", "regex", "tag")} + + Arguments: + math_priority - (int) priority of math operations. As math module has only functions - all of functions'll + have the same priority; + tag_operators - (string) string tag for operators; + tag_constants - (string) string tag for constants; + tuple_template - template of tuple to create cool tuples in return dictionary. + + Returns: + math_opeators - (dict) dictionary of operators. + math_constants - (dict) dictionary of constants. + + Raises: + No raises. """ pattern = r"\(.*\)" @@ -306,6 +388,21 @@ def get_math_operators(math_priority, tag_operators, tag_constants, tuple_templa @staticmethod def lexer(characters, token_exprs, tuple_template): + """ + Creates list of tokens from passed string. + + Arguments: + characters - (string) input string with math expression. + token_exprs - (list) input list with regulary expressions of math operands, constants and numbers. + tuple_template - template of tuple to create cool tuples from received data. + + Returns: + tokens - (list) list of tokens from passed string. + + Raises: + RuntimeError - Illegal character! - in case of unknown math operand or operator. + """ + pos = 0 tokens = [] while pos < len(characters): @@ -321,7 +418,7 @@ def lexer(characters, token_exprs, tuple_template): tokens.append(token) break if not match: - raise RuntimeError('ERROR: Illegal character: %s\n' % characters[pos]) + raise RuntimeError('ERROR in lexer: Illegal character: %s\n' % characters[pos]) else: pos = match.end(0) return tokens @@ -339,6 +436,8 @@ def main(): if __name__ == '__main__': main() +# print(help(PyCalc.lexer)) +# # calc = PyCalc() -# result = calc.calculate('sin(e^log(e^e^sin(23.0),45.0) + cos(3.0+log10(e^-e)))') +# result = calc.calculate('5.+.5') # print(result) From d93cd2885c8a5bfc2c83d56d74c6c709e83c07d5 Mon Sep 17 00:00:00 2001 From: Gleb Date: Tue, 11 Dec 2018 16:43:50 +0300 Subject: [PATCH 13/18] Pycalc release v1.1.2. Little bugfix. --- final_task/pycalc/pycalc.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/final_task/pycalc/pycalc.py b/final_task/pycalc/pycalc.py index 6442051..ebff387 100644 --- a/final_task/pycalc/pycalc.py +++ b/final_task/pycalc/pycalc.py @@ -147,7 +147,7 @@ def tokenizer(self, input_string): RuntimeError - Brackets aren't balanced! - in case of unbalanced brackets. """ - pattern = r"[0-9\.*][ \n\t]+[\.*0-9]" + pattern = r"[0-9][ \n\t]+[0-9]" if re.search(pattern, input_string): raise RuntimeError("Unknown syntax: " + re.search(pattern, input_string).group(0)) @@ -316,7 +316,7 @@ def calculate(self, input_string): try: stacked_string = self.tokenizer(input_string) except Exception as eerror: - print("ERROR in tokenizer: " + str(eerror.args[0])) + print("ERROR: in tokenizer" + str(eerror.args[0])) exit(1) # print(stacked_string) stacked_rpn = self.rpn(stacked_string) @@ -324,19 +324,19 @@ def calculate(self, input_string): try: result = self.execute_rpn(stacked_rpn) except IndexError: - print("ERROR in execute_rpn: Wrong operations order!") + print("ERROR: in execute_rpn Wrong operations order!") exit(1) except ZeroDivisionError: - print("ERROR in execute_rpn: Division by zero!") + print("ERROR: in execute_rpn Division by zero!") exit(1) except RuntimeError as rerror: - print("ERROR in execute_rpn:" + str(rerror.args[0])) + print("ERROR: in execute_rpn " + str(rerror.args[0])) exit(1) except ValueError: - print("ERROR in execute_rpn: unknown operand!") + print("ERROR: in execute_rpn unknown operand!") exit(1) except Exception: - print("ERROR in execute_rpn: unknown error!") + print("ERROR: in execute_rpn unknown error!") exit(1) return result From e2cc493fa14478d30b94f1d59bc4846c8f3e2fb0 Mon Sep 17 00:00:00 2001 From: Gleb Date: Sat, 15 Dec 2018 17:40:42 +0300 Subject: [PATCH 14/18] Pycalc release v1.1.3. -m MODULE support added. --- final_task/pycalc/pycalc.py | 82 +++++++++++++++++++++++-------------- 1 file changed, 52 insertions(+), 30 deletions(-) diff --git a/final_task/pycalc/pycalc.py b/final_task/pycalc/pycalc.py index ebff387..ed7de39 100644 --- a/final_task/pycalc/pycalc.py +++ b/final_task/pycalc/pycalc.py @@ -3,6 +3,8 @@ import types import pickle import argparse +import inspect +import sin from collections import namedtuple @@ -24,7 +26,7 @@ class PyCalc: lexer - transforms string into token stack. This method is more common, while tokenizer has more rules to perform. """ - def __init__(self): + def __init__(self, *args): """ Pycalc class initialiser. Sets a lot of variables, such as tag variables, dictionaries of math operators, math constants, etc. and saves @@ -99,20 +101,25 @@ def __init__(self): "space": full_info(None, None, None, r'[ \n\t]+', None), "int_n": full_info(None, None, None, r'[0-9]+', self.tag_number), "int_f": full_info(None, None, None, r'[0-9]+\.[0-9]+', self.tag_number), - "int_f2": full_info(None, None, None, r'\.[0-9]', self.tag_number), + "int_f2": full_info(None, None, None, r'\.[0-9]+', self.tag_number), + "int_f3": full_info(None, None, None, r'[0-9]+\.', self.tag_number) } - math_operators, math_constants = PyCalc.get_math_operators(math_priority=math_priority, - tag_operators=self.tag_advanced, - tag_constants=self.tag_constant, - tuple_template=full_info - ) + math_operators, math_constants = PyCalc.get_operators_from_module(module=math, + priority=math_priority, + tag_operators=self.tag_advanced, + tag_constants=self.tag_constant, + tuple_template=full_info + ) + + math_operators.update({'log': full_info(math.log, math_priority, 1, 'log', self.tag_advanced)}) + math_operators.update({'log2': full_info(math.log, math_priority, 2, 'log2', self.tag_advanced)}) self.operators = common_operators self.operators.update(math_operators) - self.constants = math_constants + self.constants = math_constants.copy() token_expressions = [] for item in self.operators.values(): @@ -132,6 +139,23 @@ def __init__(self): except Exception: pass + finally: + + for item in args: + math_operators, math_constants = PyCalc.get_operators_from_module(module=item, + priority=math_priority, + tag_operators=self.tag_advanced, + tag_constants=self.tag_constant, + tuple_template=full_info + ) + self.operators.update(math_operators) + self.constants.update(math_constants) + for j_item in math_operators.values(): + self.token_exprs.append(regex_and_tag(j_item.regex, j_item.tag)) + for j_item in math_constants.values(): + self.token_exprs.append(regex_and_tag(j_item.regex, j_item.tag)) + self.token_exprs.sort(reverse=True) + def tokenizer(self, input_string): """ Creates list of tokens from passed string. @@ -341,21 +365,21 @@ def calculate(self, input_string): return result @staticmethod - def get_math_operators(math_priority, tag_operators, tag_constants, tuple_template): + def get_operators_from_module(module, priority, tag_operators, tag_constants, tuple_template): """ Parses math module and returns dictionary with followed structure: {'name': namedtuple("full_info", ("func", "priority", "number_args", "regex", "tag")} Arguments: - math_priority - (int) priority of math operations. As math module has only functions - all of functions'll + priority - (int) priority of math operations. As math module has only functions - all of functions'll have the same priority; tag_operators - (string) string tag for operators; tag_constants - (string) string tag for constants; tuple_template - template of tuple to create cool tuples in return dictionary. Returns: - math_opeators - (dict) dictionary of operators. - math_constants - (dict) dictionary of constants. + operators - (dict) dictionary of operators. + constants - (dict) dictionary of constants. Raises: No raises. @@ -364,27 +388,27 @@ def get_math_operators(math_priority, tag_operators, tag_constants, tuple_templa pattern = r"\(.*\)" coma_pattern = r"\," - math_operators = {} - math_constants = {} + operators = {} + constants = {} - for item in dir(math): - if isinstance(math.__dict__.get(item), types.BuiltinFunctionType): + for item in dir(module): + if isinstance(module.__dict__.get(item), types.BuiltinFunctionType): if item.find("__"): - res = re.search(pattern, math.__dict__.get(item).__doc__) + res = re.search(pattern, module.__dict__.get(item).__doc__) res = re.findall(coma_pattern, res.group()) - math_operators.update({item: tuple_template(math.__dict__.get(item), math_priority, len(res) + 1, - item, tag_operators)}) - + operators.update({item: tuple_template(module.__dict__.get(item), priority, len(res) + 1, + item, tag_operators)}) + elif isinstance(module.__dict__.get(item), types.FunctionType): + res = len(inspect.getfullargspec(module.__dict__.get(item)).args) + operators.update({item: tuple_template(module.__dict__.get(item), priority, res, + item, tag_operators)}) else: if item.find("__"): - math_constants.update({item: tuple_template(math.__dict__.get(item), None, None, item, - tag_constants)}) - - math_operators.update({'log': tuple_template(math.log, math_priority, 1, 'log', tag_operators)}) - math_operators.update({'log2': tuple_template(math.log, math_priority, 2, 'log2', tag_operators)}) + constants.update({item: tuple_template(module.__dict__.get(item), None, None, item, + tag_constants)}) - return math_operators, math_constants + return operators, constants @staticmethod def lexer(characters, token_exprs, tuple_template): @@ -436,8 +460,6 @@ def main(): if __name__ == '__main__': main() -# print(help(PyCalc.lexer)) -# -# calc = PyCalc() -# result = calc.calculate('5.+.5') +# calc = PyCalc(sin) +# result = calc.calculate('sin(5)+z+x+cos(5)') # print(result) From 3a02e59e0e3bee4717cda537e3961b6b76539cba Mon Sep 17 00:00:00 2001 From: Gleb Date: Sat, 15 Dec 2018 17:43:53 +0300 Subject: [PATCH 15/18] Pycalc release v1.1.3.1. Really tiny bygfix. --- final_task/pycalc/pycalc.py | 1 - 1 file changed, 1 deletion(-) diff --git a/final_task/pycalc/pycalc.py b/final_task/pycalc/pycalc.py index ed7de39..6086757 100644 --- a/final_task/pycalc/pycalc.py +++ b/final_task/pycalc/pycalc.py @@ -4,7 +4,6 @@ import pickle import argparse import inspect -import sin from collections import namedtuple From fd8a9d22bbb7b46bf67439b8f22b0dc8929e25f3 Mon Sep 17 00:00:00 2001 From: Gleb Date: Sat, 15 Dec 2018 20:16:25 +0300 Subject: [PATCH 16/18] Pycalc release v1.1.4. Log problem solved. -m MODULE updated. Code refactoring. --- final_task/pycalc/pycalc.py | 244 +++++++++++++++++++++--------------- 1 file changed, 140 insertions(+), 104 deletions(-) diff --git a/final_task/pycalc/pycalc.py b/final_task/pycalc/pycalc.py index 6086757..b44a562 100644 --- a/final_task/pycalc/pycalc.py +++ b/final_task/pycalc/pycalc.py @@ -13,8 +13,8 @@ class PyCalc: Evaluates the passed string. List of methods: - __init__ - class initialiser; - tokenizer - creates list of tokens from passed string; + __init__ - class initializer; + tokenizer - creates list of tokens from passed string; rpn - transforms passed stack into reverse polish notation stack; execute_rpn - executes passed stack (stack have to be an rpn stack) calculate - calculates passed string using previous methods; @@ -25,15 +25,16 @@ class PyCalc: lexer - transforms string into token stack. This method is more common, while tokenizer has more rules to perform. """ + def __init__(self, *args): """ - Pycalc class initialiser. + Pycalc class initializer. Sets a lot of variables, such as tag variables, dictionaries of math operators, math constants, etc. and saves them into pickle file. On the next run this file'll be unpacked and loaded, so there'll be no need to recalcula- - te dictionaries of math operators and constants. + te dictionaries of math operators and constants. User functions won't be added to pickle file. Arguments: - No arguments. + *args - (list) list of additional modules. Returns: No returns. @@ -47,92 +48,82 @@ def __init__(self, *args): cfg_name = "operators.pkl" - self.tag_advanced = 'advanced' - self.tag_constant = 'constant' - self.tag_common = 'common' - self.tag_number = 'number' + tag_advanced = 'advanced' + tag_constant = 'constant' + tag_common = 'common' + tag_number = 'number' + + math_priority = 8 try: with open(cfg_name, 'rb') as pickle_file: obj = pickle.load(pickle_file) - self.constants, self.operators, self.token_exprs = obj + math_constants, math_operators, token_expressions = obj except Exception: - math_priority = 8 - common_operators = { - ",": full_info(None, 9, None, r',', self.tag_common), - - - "abs": full_info(float.__abs__, 8, 1, r'abs', self.tag_common), - "round": full_info(float.__round__, 8, 1, r'round', self.tag_common), - - "$": full_info(float.__pos__, 7, 1, r'\$', self.tag_common), - "#": full_info(float.__neg__, 7, 1, r'\#', self.tag_common), + ",": full_info(None, 9, None, r',', tag_common), - "^": full_info(float.__pow__, 6, 2, r'\^', self.tag_common), + "abs": full_info(float.__abs__, 8, 1, r'abs', tag_common), + "round": full_info(float.__round__, 8, 1, r'round', tag_common), - "*": full_info(float.__mul__, 5, 2, r'\*', self.tag_common), - "/": full_info(float.__truediv__, 5, 2, r'/', self.tag_common), + "$": full_info(float.__pos__, 7, 1, r'\$', tag_common), + "#": full_info(float.__neg__, 7, 1, r'\#', tag_common), - "%": full_info(float.__mod__, 4, 2, r'%', self.tag_common), - "//": full_info(float.__floordiv__, 4, 2, r'//', self.tag_common), + "^": full_info(float.__pow__, 6, 2, r'\^', tag_common), - "-": full_info(float.__sub__, 2, 2, r'-', self.tag_common), - "+": full_info(float.__add__, 2, 2, r'\+', self.tag_common), + "*": full_info(float.__mul__, 5, 2, r'\*', tag_common), + "/": full_info(float.__truediv__, 5, 2, r'/', tag_common), + "%": full_info(float.__mod__, 4, 2, r'%', tag_common), + "//": full_info(float.__floordiv__, 4, 2, r'//', tag_common), + "-": full_info(float.__sub__, 2, 2, r'-', tag_common), + "+": full_info(float.__add__, 2, 2, r'\+', tag_common), - "(": full_info(None, 1, 0, r'\(', self.tag_common), - - "<=": full_info(float.__le__, 0, 2, r'<=', self.tag_common), - ">=": full_info(float.__ge__, 0, 2, r'>=', self.tag_common), - "==": full_info(float.__eq__, 0, 2, r'==', self.tag_common), - "!=": full_info(float.__ne__, 0, 2, r'!=', self.tag_common), - "<": full_info(float.__lt__, 0, 2, r'<', self.tag_common), - ">": full_info(float.__gt__, 0, 2, r'>', self.tag_common), - - ")": full_info(None, None, None, r'\)', self.tag_common), - - "space": full_info(None, None, None, r'[ \n\t]+', None), - "int_n": full_info(None, None, None, r'[0-9]+', self.tag_number), - "int_f": full_info(None, None, None, r'[0-9]+\.[0-9]+', self.tag_number), - "int_f2": full_info(None, None, None, r'\.[0-9]+', self.tag_number), - "int_f3": full_info(None, None, None, r'[0-9]+\.', self.tag_number) + "(": full_info(None, 1, 0, r'\(', tag_common), + "<=": full_info(float.__le__, 0, 2, r'<=', tag_common), + ">=": full_info(float.__ge__, 0, 2, r'>=', tag_common), + "==": full_info(float.__eq__, 0, 2, r'==', tag_common), + "!=": full_info(float.__ne__, 0, 2, r'!=', tag_common), + "<": full_info(float.__lt__, 0, 2, r'<', tag_common), + ">": full_info(float.__gt__, 0, 2, r'>', tag_common), + ")": full_info(None, None, None, r'\)', tag_common), + "space": full_info(None, None, None, r'[ \n\t]+', None), + "num_i": full_info(None, None, None, r'[0-9]+', tag_number), + "num_f": full_info(None, None, None, r'[0-9]+\.[0-9]+', tag_number), + "num_f2": full_info(None, None, None, r'\.[0-9]+', tag_number), + "num_f3": full_info(None, None, None, r'[0-9]+\.', tag_number) } math_operators, math_constants = PyCalc.get_operators_from_module(module=math, priority=math_priority, - tag_operators=self.tag_advanced, - tag_constants=self.tag_constant, + tag_operators=tag_advanced, + tag_constants=tag_constant, tuple_template=full_info ) - math_operators.update({'log': full_info(math.log, math_priority, 1, 'log', self.tag_advanced)}) - math_operators.update({'log2': full_info(math.log, math_priority, 2, 'log2', self.tag_advanced)}) + # math_operators.update({'log': full_info(math.log, math_priority, 1, 'log', tag_advanced)}) + # math_operators.update({'log2': full_info(math.log, math_priority, 2, 'log2', tag_advanced)}) - self.operators = common_operators - self.operators.update(math_operators) - self.constants = math_constants.copy() + math_operators.update(common_operators) token_expressions = [] - for item in self.operators.values(): + for item in math_operators.values(): token_expressions.append(regex_and_tag(item.regex, item.tag)) - for item in self.constants.values(): + for item in math_constants.values(): token_expressions.append(regex_and_tag(item.regex, item.tag)) - token_expressions.sort(reverse=True) - self.token_exprs = token_expressions try: - obj = [self.constants, - self.operators, - self.token_exprs] + obj = [math_constants, + math_operators, + token_expressions] with open(cfg_name, 'wb') as pickle_file: pickle.dump(obj, pickle_file) except Exception: @@ -140,20 +131,30 @@ def __init__(self, *args): finally: - for item in args: - math_operators, math_constants = PyCalc.get_operators_from_module(module=item, - priority=math_priority, - tag_operators=self.tag_advanced, - tag_constants=self.tag_constant, - tuple_template=full_info - ) - self.operators.update(math_operators) - self.constants.update(math_constants) - for j_item in math_operators.values(): - self.token_exprs.append(regex_and_tag(j_item.regex, j_item.tag)) - for j_item in math_constants.values(): - self.token_exprs.append(regex_and_tag(j_item.regex, j_item.tag)) - self.token_exprs.sort(reverse=True) + if args: + + for item in args: + temp_operators, temp_constants = PyCalc.get_operators_from_module(module=item, + priority=math_priority, + tag_operators=tag_advanced, + tag_constants=tag_constant, + tuple_template=full_info) + math_operators.update(temp_operators) + math_constants.update(temp_constants) + for j_item in temp_operators.values(): + token_expressions.append(regex_and_tag(j_item.regex, j_item.tag)) + for j_item in temp_constants.values(): + token_expressions.append(regex_and_tag(j_item.regex, j_item.tag)) + token_expressions.sort(reverse=True) + + self.token_expressions = token_expressions + self.constants = math_constants + self.operators = math_operators + + self.tag_common = tag_common + self.tag_advanced = tag_advanced + self.tag_number = tag_number + self.tag_constant = tag_constant def tokenizer(self, input_string): """ @@ -169,11 +170,12 @@ def tokenizer(self, input_string): RuntimeError - Unknown syntax! - in case of spaces between numbers. RuntimeError - Brackets aren't balanced! - in case of unbalanced brackets. """ - + # Checking spaces between numbers pattern = r"[0-9][ \n\t]+[0-9]" if re.search(pattern, input_string): raise RuntimeError("Unknown syntax: " + re.search(pattern, input_string).group(0)) + # Checking unary operations patterns_and_replacements = [ (r"--", r"+"), (r"\++\+", r"+"), @@ -192,19 +194,22 @@ def tokenizer(self, input_string): break_bool = True break - pattern = r"log\(.+," - tmp = re.search(pattern, input_string) - while tmp: - pos = tmp.start() - first_part = input_string[:pos] - second_part = input_string[pos:] - second_part = re.sub("^log", "log2", second_part) - input_string = first_part + second_part - tmp = re.search(pattern, input_string) - - token_and_tag = namedtuple("str_and_tag", ("token", "tag")) - token_stack = PyCalc.lexer(input_string, self.token_exprs, token_and_tag) - + # Solving the log problem + # pattern = r"log\(.+," + # tmp = re.search(pattern, input_string) + # while tmp: + # pos = tmp.start() + # first_part = input_string[:pos] + # second_part = input_string[pos:] + # second_part = re.sub("^log", "log2", second_part) + # input_string = first_part + second_part + # tmp = re.search(pattern, input_string) + + # Lexing + token_and_tag = namedtuple("token_and_tag", ("token", "tag")) + token_stack = PyCalc.lexer(input_string, self.token_expressions, token_and_tag) + + # Checking bracket balance, adding implicit multiplications, replacing unary operations temporary_stack = ["$"] prev_item = token_and_tag("$", self.tag_common) bracket_balance = 0 @@ -220,7 +225,7 @@ def tokenizer(self, input_string): if ((item.tag == self.tag_constant or item.tag == self.tag_advanced or item.tag == self.tag_number) and (prev_item.token == ")" or prev_item.tag == self.tag_constant or prev_item.tag == self.tag_number)) or \ - ((prev_item.tag == self.tag_constant or prev_item.tag == self.tag_number) and item.token == "("): + ((prev_item.tag == self.tag_constant or prev_item.tag == self.tag_number) and item.token == "("): temporary_stack.append("*") if prev_item.tag == self.tag_common and prev_item.token != ")": @@ -237,6 +242,29 @@ def tokenizer(self, input_string): if bracket_balance != 0: raise RuntimeError("Brackets aren't balanced!") + # Solving the log problem + i = 0 + while i < len(token_stack): + if token_stack[i] == "log": + j = i+2 + bracket_counter = 1 + while bracket_counter != 0: + if token_stack[j] == "(": + bracket_counter += 1 + elif token_stack[j] == ")": + bracket_counter -= 1 + elif token_stack[j] == "," and bracket_counter == 1: + break + + j += 1 + + else: + token_stack.insert(j-1, ",") + token_stack.insert(j, "e") + + i += 1 + + return token_stack def rpn(self, stack): @@ -337,15 +365,15 @@ def calculate(self, input_string): # print(input_string) try: - stacked_string = self.tokenizer(input_string) + tokens = self.tokenizer(input_string) except Exception as eerror: print("ERROR: in tokenizer" + str(eerror.args[0])) exit(1) - # print(stacked_string) - stacked_rpn = self.rpn(stacked_string) + # print(tokens) + rpn = self.rpn(tokens) # print(stacked_rpn) try: - result = self.execute_rpn(stacked_rpn) + result = self.execute_rpn(rpn) except IndexError: print("ERROR: in execute_rpn Wrong operations order!") exit(1) @@ -366,10 +394,11 @@ def calculate(self, input_string): @staticmethod def get_operators_from_module(module, priority, tag_operators, tag_constants, tuple_template): """ - Parses math module and returns dictionary with followed structure: + Parses passed module and returns dictionary with followed structure: {'name': namedtuple("full_info", ("func", "priority", "number_args", "regex", "tag")} Arguments: + module - (module) input module to parse; priority - (int) priority of math operations. As math module has only functions - all of functions'll have the same priority; tag_operators - (string) string tag for operators; @@ -383,8 +412,8 @@ def get_operators_from_module(module, priority, tag_operators, tag_constants, tu Raises: No raises. """ - - pattern = r"\(.*\)" + any_pattern = r"\(.+\)" + brackets_pattern = r"\(.*\)" coma_pattern = r"\," operators = {} @@ -392,21 +421,22 @@ def get_operators_from_module(module, priority, tag_operators, tag_constants, tu for item in dir(module): if isinstance(module.__dict__.get(item), types.BuiltinFunctionType): - if item.find("__"): - res = re.search(pattern, module.__dict__.get(item).__doc__) - res = re.findall(coma_pattern, res.group()) - operators.update({item: tuple_template(module.__dict__.get(item), priority, len(res) + 1, - item, tag_operators)}) + res = re.search(brackets_pattern, module.__dict__.get(item).__doc__) + if re.search(any_pattern, res.group()): + res = re.findall(coma_pattern, res.group()) + operators.update({item: tuple_template(module.__dict__.get(item), priority, len(res) + 1, + item, tag_operators)}) + else: + operators.update({item: tuple_template(module.__dict__.get(item), priority, 0, + item, tag_operators)}) elif isinstance(module.__dict__.get(item), types.FunctionType): res = len(inspect.getfullargspec(module.__dict__.get(item)).args) - operators.update({item: tuple_template(module.__dict__.get(item), priority, res, - item, tag_operators)}) + operators.update({item: tuple_template(module.__dict__.get(item), priority, res, item, tag_operators)}) else: if item.find("__"): constants.update({item: tuple_template(module.__dict__.get(item), None, None, item, tag_constants)}) - return operators, constants @staticmethod @@ -450,8 +480,14 @@ def lexer(characters, token_exprs, tuple_template): def main(): parser = argparse.ArgumentParser() parser.add_argument("EXPRESSION", help="expression string to evaluate") + parser.add_argument('-m', '--use-module', metavar='MODULE', type=str, nargs='+', help='additional user modules') args = parser.parse_args() - calc = PyCalc() + module_list = [] + if args.use_module: + for item in args.use_module: + module_list.append(__import__(item)) + + calc = PyCalc(*module_list) result = calc.calculate(args.EXPRESSION) print(result) @@ -459,6 +495,6 @@ def main(): if __name__ == '__main__': main() -# calc = PyCalc(sin) -# result = calc.calculate('sin(5)+z+x+cos(5)') +# calc = PyCalc() +# result = calc.calculate('log(log(e^e, e), e)') # print(result) From 52d2c69db8d78df650034690576e54589da2d189 Mon Sep 17 00:00:00 2001 From: Gleb Date: Sat, 15 Dec 2018 20:23:18 +0300 Subject: [PATCH 17/18] Pycalc release v1.1.4.1. Little bugfix. --- final_task/pycalc/pycalc.py | 1 - 1 file changed, 1 deletion(-) diff --git a/final_task/pycalc/pycalc.py b/final_task/pycalc/pycalc.py index b44a562..20aed8d 100644 --- a/final_task/pycalc/pycalc.py +++ b/final_task/pycalc/pycalc.py @@ -264,7 +264,6 @@ def tokenizer(self, input_string): i += 1 - return token_stack def rpn(self, stack): From 221facb0d78d5a41134cb16dd38f6693c203bda9 Mon Sep 17 00:00:00 2001 From: Gleb Date: Sun, 16 Dec 2018 14:45:14 +0300 Subject: [PATCH 18/18] Pycalc release v1.1.5. Doc-string update. Code style update. --- final_task/pycalc/pycalc.py | 64 +++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 34 deletions(-) diff --git a/final_task/pycalc/pycalc.py b/final_task/pycalc/pycalc.py index 20aed8d..c424f51 100644 --- a/final_task/pycalc/pycalc.py +++ b/final_task/pycalc/pycalc.py @@ -31,7 +31,8 @@ def __init__(self, *args): Pycalc class initializer. Sets a lot of variables, such as tag variables, dictionaries of math operators, math constants, etc. and saves them into pickle file. On the next run this file'll be unpacked and loaded, so there'll be no need to recalcula- - te dictionaries of math operators and constants. User functions won't be added to pickle file. + te dictionaries of math operators and constants. After this method parses all passed modules and retrieves + functions and constants form them. User functions and constants won't be added to pickle file. Arguments: *args - (list) list of additional modules. @@ -108,9 +109,6 @@ def __init__(self, *args): tuple_template=full_info ) - # math_operators.update({'log': full_info(math.log, math_priority, 1, 'log', tag_advanced)}) - # math_operators.update({'log2': full_info(math.log, math_priority, 2, 'log2', tag_advanced)}) - math_operators.update(common_operators) token_expressions = [] @@ -158,7 +156,8 @@ def __init__(self, *args): def tokenizer(self, input_string): """ - Creates list of tokens from passed string. + Creates list of tokens from passed string. Lexer method is used as common tokenizer. Adds a lot of additional + rules such as checking unary operations, logs, bracket balance etc to lexer result. Arguments: input_string - (string) input string with math expression. @@ -194,17 +193,6 @@ def tokenizer(self, input_string): break_bool = True break - # Solving the log problem - # pattern = r"log\(.+," - # tmp = re.search(pattern, input_string) - # while tmp: - # pos = tmp.start() - # first_part = input_string[:pos] - # second_part = input_string[pos:] - # second_part = re.sub("^log", "log2", second_part) - # input_string = first_part + second_part - # tmp = re.search(pattern, input_string) - # Lexing token_and_tag = namedtuple("token_and_tag", ("token", "tag")) token_stack = PyCalc.lexer(input_string, self.token_expressions, token_and_tag) @@ -268,7 +256,7 @@ def tokenizer(self, input_string): def rpn(self, stack): """ - Transforms list of tokens to its rpn (reverse polish notation) form. + Transforms list of tokens to its rpn (reverse polish notation) form. Uses common RPN algorithm. Arguments: stack - (list) input stack of tokens. @@ -317,7 +305,7 @@ def rpn(self, stack): def execute_rpn(self, rpn_stack): """ - Executes passed stack in rpn form and returns the result or raises error. + Executes passed stack in rpn form and returns the result or raises error. Uses common RPN-execution algorithm. Arguments: rpn_stack - (list) input stack of tokens in rpn form. @@ -361,40 +349,53 @@ def execute_rpn(self, rpn_stack): return result def calculate(self, input_string): + """ + Calculates passed string and takes care of all errors, that happens in program running time. + + Arguments: + input_string - (string) input string with math expression. + + Returns: + result - (float) resulting number. + + Raises: + No raises. + """ - # print(input_string) try: tokens = self.tokenizer(input_string) except Exception as eerror: print("ERROR: in tokenizer" + str(eerror.args[0])) exit(1) - # print(tokens) + rpn = self.rpn(tokens) - # print(stacked_rpn) + try: result = self.execute_rpn(rpn) except IndexError: print("ERROR: in execute_rpn Wrong operations order!") exit(1) except ZeroDivisionError: - print("ERROR: in execute_rpn Division by zero!") + print("ERROR: in execute_rpn Division by zero!") exit(1) except RuntimeError as rerror: print("ERROR: in execute_rpn " + str(rerror.args[0])) exit(1) except ValueError: - print("ERROR: in execute_rpn unknown operand!") + print("ERROR: in execute_rpn Unknown operand!") exit(1) except Exception: - print("ERROR: in execute_rpn unknown error!") + print("ERROR: in execute_rpn Unknown error!") exit(1) + return result @staticmethod def get_operators_from_module(module, priority, tag_operators, tag_constants, tuple_template): """ Parses passed module and returns dictionary with followed structure: - {'name': namedtuple("full_info", ("func", "priority", "number_args", "regex", "tag")} + {'name': ("func", "priority", "number_args", "regex", "tag")} + Uses namedtuple to create more readable code. Arguments: module - (module) input module to parse; @@ -434,18 +435,17 @@ def get_operators_from_module(module, priority, tag_operators, tag_constants, tu operators.update({item: tuple_template(module.__dict__.get(item), priority, res, item, tag_operators)}) else: if item.find("__"): - constants.update({item: tuple_template(module.__dict__.get(item), None, None, item, - tag_constants)}) + constants.update({item: tuple_template(module.__dict__.get(item), None, None, item, tag_constants)}) return operators, constants @staticmethod - def lexer(characters, token_exprs, tuple_template): + def lexer(characters, token_expressions, tuple_template): """ Creates list of tokens from passed string. Arguments: characters - (string) input string with math expression. - token_exprs - (list) input list with regulary expressions of math operands, constants and numbers. + token_expressions - (list) input list with regulary expressions of math operands, constants and numbers. tuple_template - template of tuple to create cool tuples from received data. Returns: @@ -459,7 +459,7 @@ def lexer(characters, token_exprs, tuple_template): tokens = [] while pos < len(characters): match = None - for token_expr in token_exprs: + for token_expr in token_expressions: pattern, tag = token_expr regex = re.compile(pattern) match = regex.match(characters, pos) @@ -493,7 +493,3 @@ def main(): if __name__ == '__main__': main() - -# calc = PyCalc() -# result = calc.calculate('log(log(e^e, e), e)') -# print(result)