diff --git a/final_task/pycalc/CheckAndChange.py b/final_task/pycalc/CheckAndChange.py new file mode 100644 index 00000000..c940fb51 --- /dev/null +++ b/final_task/pycalc/CheckAndChange.py @@ -0,0 +1,87 @@ +import re +import pycalc.operators as operators +import pycalc.difcalc as difcalc +from numbers import Number +import importlib.util +from os import path + + +class CheckAndChange(): + + def do_all_changes(self, expr, module): + self.add_args(module) + + if not re.search(r'[0-9]+', expr): + const = re.search(r'[A-ZAa-z]+', expr) + if not const: + raise Exception("No Numbers in expression") + else: + if const[0] not in difcalc.ComplexCalc.const and const[0] not in difcalc.ComplexCalc.math_functions: + raise Exception("Check your const or function") + + expr = expr.replace("//", "&") + self.correct_brackets(expr) + self.correct_spaces(expr) + expr = expr.replace(" ", "") + + return expr + + def add_args(self, modul): + if modul: + base = path.basename(modul) + + module_name = path.splitext(base)[0] + spec = importlib.util.spec_from_file_location(module_name, modul) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + new_functions = { + attr: getattr(module, attr) for attr in dir(module) if callable(getattr(module, attr)) + } + difcalc.ComplexCalc.math_functions.update(new_functions) + + new_const = { + attr: getattr(module, attr) for attr in dir(module)if isinstance(getattr(module, attr), Number) + } + difcalc.ComplexCalc.const.update(new_const) + + def correct_spaces(self, expr): + searcher = expr.find(" ") + expression = expr + + while searcher != -1 and expression != "": + if searcher != len(expression) - 1 and searcher != 0: + if expression[searcher - + 1].isdigit() and expression[searcher + 1].isdigit(): + raise Exception("must not be 'digit' 'space' 'digit'") + + if expression[searcher - 1] in operators.operators \ + and expression[searcher + 1] in operators.operators: + raise Exception( + "must not be 'operator' 'space' 'operator'") + + if expression[searcher - 1] in difcalc.ComplexCalc.compare \ + and expression[searcher + 1] in difcalc.ComplexCalc.compare: + raise Exception("Check your spaces betwin") + + expression = expression[searcher + 1:] + searcher = expression.find(" ") + else: + if searcher == len(expression) - 1: + break + if searcher == 0: + expression = expression[1:] + + def correct_brackets(self, expr): + counter = 0 + for one in expr: + + if one == "(": + counter += 1 + elif one == ")": + counter -= 1 + if counter < 0: + raise Exception("check brackets! ") + else: + if counter != 0: + raise Exception("check brackets! ") diff --git a/final_task/__init__.py b/final_task/pycalc/__init__.py similarity index 100% rename from final_task/__init__.py rename to final_task/pycalc/__init__.py diff --git a/final_task/pycalc/difcalc.py b/final_task/pycalc/difcalc.py new file mode 100644 index 00000000..2af35c27 --- /dev/null +++ b/final_task/pycalc/difcalc.py @@ -0,0 +1,151 @@ +import re +import pycalc.easyCalculation as easyCalculation +import math +from numbers import Number + + +class ComplexCalc(easyCalculation.Calculator): + """more functional calculator than "Calculator" """ + + def __init__(self): + """initializing data from "math" """ + ComplexCalc.const = { + **{attr: getattr(math, attr) for attr in dir(math) if isinstance(getattr(math, attr), Number)}, + **{"True": 1, "False": 0} + } + + ComplexCalc.math_functions = { + **{attr: getattr(math, attr) for attr in dir(math) if callable(getattr(math, attr))}, + **{"abs": lambda a: abs(a), + "round": lambda a: round(a), + "pow": lambda a, b: pow(a, b)} + } + + def expression_search(self, expr): + """search function or constant and calc power if function negative""" + + while True: + + func = re.search(r'[A-ZAa-z]+1?0?', expr) + + if func is None: + return self.search_braсkets(expr) + + afterExpr = func.end() + place = func.start() + if func[0] in self.const: + + rezult = self.const[func[0]] + expr = expr[:place] + str(rezult) + expr[afterExpr:] + continue + + searcher = 0 + count = 1 + for one in expr[afterExpr + 1:]: + + searcher += 1 + if one == ")": + count -= 1 + if one == "(": + count += 1 + if count == 0: + break + end = searcher + afterExpr + # выкинуть если конец строки + if expr[afterExpr] != '(': + + raise Exception( + "the expression must be written in the following way 'function(expression)'") + + else: + + rezult = self._find_replacement( + func[0], expr[afterExpr + 1:end]) + expr = expr[:place] + rezult + expr[end + 1:] + if float(rezult) < 0: + end = place + len(rezult) - 1 + expr = self._calc_if_power(expr, place, end) + + def _find_replacement(self, func, expr): + """count all arguments from function""" + if func in ComplexCalc.math_functions: + allargs = self._commasplit(expr) + + float_args = [] + for each in allargs: + float_args.append(float(self.expression_search(each))) + + rezult = '{:.15f}'.format( + ComplexCalc.math_functions[func]( + *float_args)) + + else: + + raise Exception("Indefined function") + return str(rezult) + + def _commasplit(self, expr: str): + """search arguments for function""" + breketscounter = 0 + preve = 0 + count = 1 + split = [] + for each in expr: + if breketscounter == 0 and each == ",": + split.append(expr[preve:count - 1]) + preve = count + + elif each == "(": + breketscounter += 1 + elif each == ")": + breketscounter -= 1 + count += 1 + + split.append(expr[preve:count]) + + return split + compare = { + + ">": lambda a, b: a > b, + ">=": lambda a, b: a >= b, + "<=": lambda a, b: a <= b, + "==": lambda a, b: a == b, + "<": lambda a, b: a < b, + "!=": lambda a, b: a != b + + + } + + def calculate(self, expr): + """begin of calculatoion, search compare""" + place = re.search(r'(>=)|(>)|(<=)|(<)|(!=)|(==)', expr) + + while place: + after = re.search(r'(>=)|(>)|(<=)|(<)|(!=)|(==)', + expr[place.end():]) + number_one = self.expression_search(expr[:place.start()]) + + if not after: + number_two = self.expression_search(expr[place.end():]) + else: + number_two = self.expression_search( + expr[place.end():after.start() + place.end()]) + + if number_one is not None and number_two is not None: + rezult = ComplexCalc.compare[place[0]](number_one, number_two) + end = "" + + if after: + if after.start() == 0: + raise Exception("no symbols between compare") + end = expr[after.end() + place.end():] + expr = str(rezult) + after[0] + end + else: + return rezult + + else: + raise Exception( + "uncorrect expression must be 'expr' operator 'expr'") + place = re.search(r'(>=)|(>)|(<=)|(<)|(!=)|(==)', expr) + + return self.expression_search(expr) diff --git a/final_task/pycalc/easyCalculation.py b/final_task/pycalc/easyCalculation.py new file mode 100644 index 00000000..853dfbeb --- /dev/null +++ b/final_task/pycalc/easyCalculation.py @@ -0,0 +1,140 @@ +import re +import pycalc.operators as operators + + +class Calculator(): + """calculator for counting simple expression""" + + def _calculation(self, expr): + """find all binary operation, "^" work in a different way """ + + place = expr.rfind("^") + + while place != -1: + findBefore = self.search_simple_number(expr[:place]) + begin = place - findBefore.end() + findAfter = self.search_number_from_begin(expr[place:]) + end = place + findAfter.end() + expr = self.__binary_operation(expr, begin, place, end) + + place = expr.rfind("^") + + place = re.search(r'/|\*|%|&', expr) + + while place: + point = place.start() + findBefore = self.search_number_from_end(expr[:point]) + begin = point - findBefore.end() + 1 + findAfter = self.search_number_from_begin(expr[point:]) + end = point + findAfter.end() + expr = self.__binary_operation(expr, begin, point, end) + place = re.search(r'/|\*|%|&', expr) + + return self.sum(expr) + + def sum(self, expr): + """sum all elements as elements with binary minus""" + + if expr[-1] == "+" or expr[-1] == "-": + raise Exception( + "'+' or '-' and bla-bla-blah mustn' be the last even in brackets") + + summing = 0 + number = 0 + while expr != "": + find = re.search(r'([+-]+)?([0-9]+([.][0-9]*)?|[.][0-9]+)', expr) + if find.start() != 0: + raise Exception("Undefine operator") + number = find[0] + + expr = expr[find.end():] + a = self.unary_rezult(number) + summing += a + return summing + + def unary_rezult(self, number): + """just coubt all minuses and define symbol of number""" + minus = number.count("-") + plus = number.count("+") + real_number = number[plus + minus:] + if minus % 2 == 1: + real_number = float("-" + real_number) + else: + real_number = float(real_number) + return real_number + + def search_braсkets(self, expr): + """search lowest bracket's level""" + expr = expr.replace(" ", "") + + while "(" in expr: + + end = expr.find(")") + if end != len(expr) - 1\ + and expr[end + 1] not in operators.operators: + Exception("no operator after brackets") + + begin = expr[:end].rfind("(") + if begin + 1 == end: + raise Exception("no Number in brakets") + + if begin != 0 and expr[begin - 1] not in operators.operators \ + and expr[begin - 1] != '-' and expr[begin - 1] != '+': + raise Exception("no operators before brackets") + + rezult = self._calculation("+" + expr[begin + 1:end]) + expr = expr[:begin] + str(rezult) + expr[end + 1:] + + if rezult < 0: + end = begin + len(str(rezult)) - 1 + expr = self._calc_if_power(expr, begin, end) + + rezult = self._calculation("+" + expr) + + return rezult + + def _calc_if_power(self, expr, begin, braket): + """power doesn't know about unary minus, therefore it's calculate separately""" + place = braket + 1 + if braket is not len(expr) - 1 and expr[place] is "^": + findAfter = self.search_number_from_begin(expr[place:]) + end = place + findAfter.end() + expr = self.__binary_operation(expr, begin, place, end) + return expr + + def __binary_operation(self, expr, begin, place, end): + """calculate binary operation""" + rezult = '{:.15f}'.format(operators.operators[expr[place]]( + self.unary_rezult(expr[begin:place]), self.unary_rezult(expr[place + 1:end]))) + + before = expr[:begin] + after = expr[end:] + expr = before + rezult + after + return expr + + def search_number_from_begin(self, expr): + """searching number after operatior""" + number = re.search( + r'([+-]+)?([0-9]+([.][0-9]*)?|[.][0-9]+)', expr) + + if not number or number.start() != 1: + raise Exception( + "the expression should be written in the following form 'number operator number'") + + return number + + def search_number_from_end(self, expr): + """searching number before operator""" + number = re.search( + r'([0-9]+([.][0-9]*)?|[.][0-9]+)([+-]+)?', expr[::-1]) + + if not number or number.start() != 0: + raise Exception( + "the expression should be written in the following form 'number operator number'") + + return number + + def search_simple_number(self, expr): + """search number before power""" + number = re.search(r'([0-9]+([.][0-9]*)?|[.][0-9]+)', expr[::-1]) + return number diff --git a/final_task/pycalc/operators.py b/final_task/pycalc/operators.py new file mode 100644 index 00000000..5c818e13 --- /dev/null +++ b/final_task/pycalc/operators.py @@ -0,0 +1,10 @@ +import operator + + +operators = { + "^": operator.pow, + "*": operator.mul, + "/": operator.truediv, + "%": operator.mod, + "&": operator.floordiv, +} diff --git a/final_task/pycalc/pycalc.py b/final_task/pycalc/pycalc.py new file mode 100644 index 00000000..e98bcbcf --- /dev/null +++ b/final_task/pycalc/pycalc.py @@ -0,0 +1,29 @@ +import pycalc.difcalc as difcalc +import pycalc.CheckAndChange as CheckAndChange +import argparse + + +calculator = difcalc.ComplexCalc() +cheker = CheckAndChange. CheckAndChange() + +parser = argparse.ArgumentParser(description='Calculation') +parser.add_argument('a', type=str, help='input your expression') +parser.add_argument('-m', type=str, help='your oun module ') + + +def start(): + args = parser.parse_args() + try: + + if args.a != "--help": + + a = cheker.do_all_changes(args.a, args.m) + a = calculator.calculate(args.a) + + else: + print("!)!))@))@)@)@))@)@)@)@)))))))") + + except Exception as e: + print("ERROR: " + str(e)) + else: + print(a) diff --git a/final_task/setup.py b/final_task/setup.py index e69de29b..6396639d 100644 --- a/final_task/setup.py +++ b/final_task/setup.py @@ -0,0 +1,13 @@ +from setuptools import setup, find_packages + +setup( + name='pycalc', + version='2.0', + packages=find_packages(), + description='calculator.', + author='Honery', + author_email='^_--', + py_modules=['pycalc'], + entry_points={'console_scripts': ['pycalc = pycalc.pycalc:start', ], }, + platforms='any', +) diff --git a/final_task/test_it.py b/final_task/test_it.py new file mode 100644 index 00000000..63a6fe62 --- /dev/null +++ b/final_task/test_it.py @@ -0,0 +1,143 @@ +import unittest +import math +import pycalc.CheckAndChange as CheckAndChange +import pycalc.difcalc as difcalc +from unittest.mock import patch + + +class TestFunctions(unittest.TestCase): + + cheker = CheckAndChange.CheckAndChange() + calculator = difcalc.ComplexCalc() + + def test_brackets(self): + self.assertEqual(self.cheker.correct_brackets("(()())"), None) + with self.assertRaises(Exception): + self.cheker.correct_brackets("(()()") + self.cheker.correct_brackets("())") + self.cheker.correct_brackets(")(") + + def test_space_cheker(self): + self.assertEqual(self.cheker.correct_spaces("1*2"), None) + self.assertEqual(self.cheker.correct_spaces("1 * 2"), None) + with self.assertRaises(Exception): + self.cheker.correct_spaces("1 2*2") + self.cheker.correct_spaces("12 * * 2") + + def test_numbers(self): + with patch('pycalc.CheckAndChange.CheckAndChange.correct_spaces'),\ + patch('pycalc.CheckAndChange.CheckAndChange.correct_brackets'),\ + patch('pycalc.CheckAndChange.CheckAndChange.add_args'): + self.assertEqual( + self.cheker.do_all_changes( + "1122113", None), "1122113") + with self.assertRaises(Exception): + self.cheker.do_all_changes("", None) + self.cheker.do_all_changes(" ", None) + + def test_module(self): + # add other + with self.assertRaises(Exception): + self.cheker.add_args("module.py") + + def calculate_simple_expression(self, expr): + expr = expr.replace("^", "**") + expr = expr.replace("&", "//") + return eval(expr) + + def test_comparison(self): + with patch('pycalc.difcalc.ComplexCalc.expression_search', new=self.calculate_simple_expression): + self.assertEqual(self.calculator.calculate( + "12+345+664+233445+2<2"), False) + self.assertEqual(self.calculator.calculate("12<1333<2000"), True) + + with self.assertRaises(Exception): + self.calculator.calculate("12+345+664+233445+2<<<2") + self.calculator.calculate("12+345+664+233445+2=2") + + def calculate_functions(self, func, expr): + return str(eval("math." + func + "(" + expr + ")")) + + def test_funtions_search(self): + with patch('pycalc.difcalc.ComplexCalc._find_replacement', new=self.calculate_functions),\ + patch('pycalc.difcalc.ComplexCalc.search_braсkets', new=self.calculate_simple_expression): + self.assertAlmostEqual( + float( + self.calculator.expression_search("sin(4)")), + math.sin(4)) + self.assertAlmostEqual( + float( + self.calculator.expression_search("sin(4)+1")), + math.sin(4) + 1) + self.assertAlmostEqual( + float( + self.calculator.expression_search("pow(2,1)+13+sin(4)")), math.pow(2, 1) + 13 + math.sin(4)) + with self.assertRaises(Exception): + self.calculator.expression_search("sin(+1") + self.calculator.expression_search("sin()+1") + self.calculator.expression_search("pow(1)+11") + self.assertAlmostEqual( + float(self.calculator.expression_search("e")), math.e) + + def test_function_calculator(self): + with patch('pycalc.difcalc.ComplexCalc._commasplit') as splitted,\ + patch('pycalc.difcalc.ComplexCalc.expression_search') as expresssearch: + + splitted.return_value = ["1"] + expresssearch.return_value = "3" + self.assertAlmostEqual( + float( + self.calculator._find_replacement( + "sin", + "1")), + math.sin(3)) + + def test_unary_operators(self): + self.assertEqual(float(self.calculator. unary_rezult("12")), 12.0) + self.assertEqual(float(self.calculator.unary_rezult("-12")), -12.0) + self.assertEqual(float(self.calculator.unary_rezult("+-12")), -12.0) + + def test_regulars_for_number(self): + self.assertEqual( + self.calculator. search_simple_number("12")[0][::-1], "12") + self.assertEqual(self.calculator. search_simple_number( + "12321424+2412+12")[0][::-1], "12") + self.assertEqual(self.calculator.search_number_from_end( + "12321424+2412+12")[0][::-1], "+12") + self.assertEqual(self.calculator.search_number_from_end( + "12321424+2412+--12")[0][::-1], "+--12") + self.assertEqual(self.calculator.search_number_from_begin( + "/12321424+2412+--12")[0], "12321424") + self.assertEqual(self.calculator.search_number_from_begin( + "*++-+-+12+2412+--12")[0], "++-+-+12") + with self.assertRaises(Exception): + self.calculator.search_number_from_begin("++-+-+12+2412+--12") + self.calculator.search_number_from_end("12321424+2412+12/") + + def test_sum(self): + with patch('pycalc.difcalc.ComplexCalc.unary_rezult') as number: + number.return_value = 1 + self.assertEqual( + self.calculator.sum("12"), 1) + self.assertEqual( + self.calculator.sum("12+12"), 2) + + with self.assertRaises(Exception): + self.calculator.sum("12+*12") + + def test_search_braсkets(self): + with patch('pycalc.difcalc.ComplexCalc._calculation', new=self.calculate_simple_expression): + self.assertEqual( + self.calculator.search_braсkets("1+(12*2)+(14+1)"), 40) + self.assertEqual( + self.calculator.search_braсkets("1+2"), 3) + self.assertEqual( + self.calculator.search_braсkets("(12+3)"), 15) + with self.assertRaises(Exception): + self.calculator.search_braсkets("12+()") + self.calculator.search_braсkets("12(12+1)") + self.calculator.search_braсkets("12+(12+1)12") + + +if __name__ == '__main__': + unittest.main()