diff --git a/final_task/pycalc.py b/final_task/pycalc.py new file mode 100644 index 00000000..be94a856 --- /dev/null +++ b/final_task/pycalc.py @@ -0,0 +1,438 @@ +""" + +The module calculates mathematical expressions. + +Functions: + parse: split an expression into numbers, functions, constants, operations; + check_expression: validates the expression entered; + transform_to_polish_notation: converts expressions according to the rules of reverse polish notation; + calculate: calculates expressions written in reverse polish notation. + +Attributes: + OPERATORS : dictionary of operations and their priority; + BUILT_IN_FUNCTION: dictionary of numeric functions; + FUNCTIONS: dictionary of mathematical functions; + CONSTANTS: dictionary of mathematical constants; + ALL_NUMBERS: possible representations of a numbers like -.5, -0.5, -5, 0.5, .5, 5, 1e-05; + INVALID_EXPRESSIONS: list of possible representations of a invalid expression. + +""" + +import argparse +import math +import operator +import re +from json import loads +from typing import List, TypeVar, Union + +OPERATORS = { + '+': (operator.add, 2), + '-': (operator.sub, 2), + '*': (operator.mul, 3), + '/': (operator.truediv, 3), + '//': (operator.floordiv, 3), + '%': (operator.mod, 3), + '^': (operator.pow, 4), + '=': (operator.eq, 0), + '==': (operator.eq, 0), + '<': (operator.lt, 0), + '<=': (operator.le, 0), + '!=': (operator.ne, 0), + '>=': (operator.ge, 0), + '>': (operator.gt, 0), +} +BUILT_IN_FUNCTION = { + 'abs': abs, + 'round': round +} +FUNCTIONS = {attr: getattr(math, attr) for attr in dir(math) if callable(getattr(math, attr))} +CONSTANTS = { + 'e': math.e, + 'pi': math.pi, + 'tau': math.tau, + 'inf': math.inf, + 'nan': math.inf +} +ALL_NUMBERS = re.compile(r'-?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?') +INVALID_EXPRESSIONS = [ + r'[\d\w()]\s+[.\d\w()]', + r'[+-/*%^=<>]$', + r'^[/*%^=<>!]', + r'[+-/*%^=<>]\)', + r'[<>/*=!]\s+[=/*]', + r'\(\)' +] + + +elements_of_expression = TypeVar('elements_of_expression') + + +def parse(expression: str) -> List[elements_of_expression]: + """Split mathematic expression, consists of 2 steps + + :param expression: String of mathematic expression + :return: Parsed line by items in the list + + """ + def parse_step1(expr: str) -> List[elements_of_expression]: + """Сreates a list with numbers, functions and constants from a mathematical expression + + :param expr: String of math expression + :return: List with items of expression + :raises: ValueError if nothing entered or invalid expression + + """ + # this regular expression contains all possible elements of the expression, + # such as numbers, constants, functions, brackets, operators + items_of_expression = r'(?:(?:[a-zA-Z]+[\d]+[\w]?' \ + r'|[a-zA-Z_]+)' \ + r'|(?:\d+(?:\.\d*)?' \ + r'|\.\d+)(?:[eE][-+]?\d+)?' \ + r'|[,+\-/*%^=<>!][=/]?|[(:)])' + for invalid_expr in INVALID_EXPRESSIONS: + if re.search(invalid_expr, expr): + raise ValueError(f'invalid expression') + if not expr: + raise ValueError(f'nothing entered') + expr = expr.replace(' ', '') + while '++' in expr or '--' in expr or '+-' in expr or '-+' in expr: + expr = re.sub(r'\+\+', '+', expr) + expr = re.sub(r'\+-', '-', expr) + expr = re.sub(r'-\+', '-', expr) + expr = re.sub(r'--', '+', expr) + parse_list = re.compile(items_of_expression).findall(expr) + return parse_list + + def parse_step2(parse_list: List[elements_of_expression]) -> List[elements_of_expression]: + """Working with minuses in expression. + + :param parse_list: List with items of expression. + :return: Updated list. + + """ + + for index, element in enumerate(parse_list): + if element == '-' and re.fullmatch(ALL_NUMBERS, parse_list[index + 1]): + if parse_list[index - 1] in '(*/%//^,': + parse_list[index] += parse_list.pop(index + 1) + elif index == len(parse_list) - 2: + if re.search(ALL_NUMBERS, parse_list[index + 1]): + parse_list[index] += parse_list.pop(index + 1) + parse_list.insert(index, '+') + elif parse_list[index + 2] in '*/%//' and re.search(ALL_NUMBERS, parse_list[index + 1]): + parse_list[index] += parse_list.pop(index + 1) + parse_list.insert(index, '+') + elif element == '-' and parse_list[index + 1] in FUNCTIONS and parse_list[index - 1] == '(': + parse_list[index] = '-1' + parse_list.insert(index + 1, '*') + return parse_list + return parse_step2(parse_step1(expression)) + + +def check_expression(parse_expression: List[elements_of_expression]): + """Contains functions that validate the input expression. + + :param parse_expression: List with items of expression + :return: List with expression elements if the expression passed all checks + + """ + def check_operators(parse_list: List[elements_of_expression]): + """Checks the validity of the entered operators. + + :param parse_list: List with items of expression. + :raise: ValueError if operators follow each other. + + """ + + for index, element in enumerate(parse_list): + if element in OPERATORS and parse_list[index + 1] in OPERATORS and parse_list[index + 1] != '-': + raise ValueError(f'unknown operation {element + parse_list[index + 1]}') + return parse_list + + def check_function_and_constants(parse_list: List[elements_of_expression]): + """Checks the validity of the entered functions or constants + + :param parse_list: List with items of expression. + :raise: ValueError if functions or constants are not supported by a calculator or are incorrectly entered. + + """ + copy_parse_expression = parse_list.copy() + for index, element in enumerate(copy_parse_expression): + if index == len(copy_parse_expression) - 1 and (element in FUNCTIONS or element in BUILT_IN_FUNCTION): + raise ValueError(f'function arguments not entered') + elif (element in FUNCTIONS or element in BUILT_IN_FUNCTION) and copy_parse_expression[index + 1] != '(': + raise ValueError(f'function arguments not entered') + elif re.fullmatch(ALL_NUMBERS, element): + copy_parse_expression.pop(index) + diff = set(copy_parse_expression).difference( + set(FUNCTIONS), + set(OPERATORS), + set(CONSTANTS), + set(BUILT_IN_FUNCTION), + {'{', '[', '(', ',', ':', ')', ']', '}'}, + {'True', 'False', 'abs_tol', 'rel_tol'} + ) + if diff: + raise ValueError(f'unknown function or constant {diff}') + else: + return parse_list + + def check_brackets(parse_list: List[elements_of_expression]): + """Check count of '(',')'. + + :param parse_list: List with items of expression. + :raise: ValueError if Amount '(' not equal to quantity ')' in expression + + """ + if parse_list.count('(') != parse_list.count(')'): + raise ValueError(f'brackets are not balanced') + else: + return parse_list + + return check_operators(check_brackets(check_function_and_constants(parse_expression))) + + +class FuncIsclose: + def __init__(self, arg1, arg2, arg3=1e-09, arg4=0.0): + self.arg1 = arg1 + self.arg2 = arg2 + self.rel_tol = arg3 + self.abs_tol = arg4 + + def calc(self): + return math.isclose(self.arg1, self.arg2, rel_tol=self.rel_tol, abs_tol=self.abs_tol) + + +def exec_isclose(parse_list: List[elements_of_expression]): + """calculates the mathematical function isclose + + :param parse_list: List with items of expression. + :return: List with isclose function result. + :raise: ValueError if entered invalid number of arguments. + """ + arguments = [] + for element in parse_list: + if element == 'isclose': + for argument in parse_list: + if re.fullmatch(ALL_NUMBERS, argument): + arguments.append(float(argument)) + if len(arguments) == 1 or len(arguments) > 4: + raise ValueError(f'invalid number of arguments') + elif len(arguments) == 2: + isclose = FuncIsclose(arguments[0], arguments[1]) + return [str(isclose.calc())] + elif len(arguments) == 3: + isclose = FuncIsclose(arguments[0], arguments[1], arguments[2]) + return [str(isclose.calc())] + elif len(arguments) == 4: + isclose = FuncIsclose(arguments[0], arguments[1], arguments[2], arguments[3]) + return [str(isclose.calc())] + else: + return parse_list + + +def transform_to_polish_notation(parse_expression: List[elements_of_expression]) -> str: + """ + + :param parse_expression: list after parsing + :return: string in reverse polish notation + + """ + stack = [] + reverse_polish_notation = '' + separator = ' ' + + def check_stack(element): + """checks what is on top of the stack and add operator to stack depending on operator priority + + :param element: operator + + """ + nonlocal reverse_polish_notation + if stack: + if stack[-1] == '(': + stack.append(element) + elif OPERATORS[element][-1] <= OPERATORS[stack[-1]][-1]: + reverse_polish_notation += stack.pop() + separator + stack.append(element) + elif OPERATORS[element][-1] > OPERATORS[stack[-1]][-1]: + stack.append(element) + elif not stack: + stack.append(element) + + def add_operators(element): + nonlocal reverse_polish_notation + if not stack: + stack.append(element) + elif element == stack[-1] and element == '^': + stack.append(element) + elif stack[-1] == '(': + stack.append(element) + elif stack[-1] in FUNCTIONS or stack[-1] in BUILT_IN_FUNCTION: + reverse_polish_notation += stack.pop() + separator + check_stack(element) + elif element == '-' and parse_expression[parse_expression.index(element) - 1] in '/*^%//': + if OPERATORS[element][-1] <= OPERATORS[stack[-1]][-1]: + stack.append(element) + reverse_polish_notation += '0' + separator + elif OPERATORS[element][-1] <= OPERATORS[stack[-1]][-1]: + reverse_polish_notation += stack.pop() + separator + check_stack(element) + else: + stack.append(element) + + def add_tokens_between_brackets(): + nonlocal reverse_polish_notation + for element in stack[::-1]: + if element == '(': + break + reverse_polish_notation += stack.pop() + separator + stack.pop() + + def add_tokens_between_bracket_and_comma(): + nonlocal reverse_polish_notation + for element in stack[::-1]: + if element == '(': + break + reverse_polish_notation += stack.pop() + separator + reverse_polish_notation += token + separator + + for token in parse_expression: + if re.fullmatch(ALL_NUMBERS, token) and float(token) >= 0: + reverse_polish_notation += token + separator + elif re.fullmatch(ALL_NUMBERS, token) and float(token) < 0: + # writes a negative number of -5 in the form of 0 5 -, according to the rules of the reverse notation + reverse_polish_notation += '0' + separator + token[1:] + separator + token[0] + separator + elif token == ')': + add_tokens_between_brackets() + elif token == ',': + add_tokens_between_bracket_and_comma() + elif token in ['(', ':']: + stack.append(token) + elif token in FUNCTIONS or token in BUILT_IN_FUNCTION: + stack.append(token) + elif token in OPERATORS: + add_operators(token) + elif token in CONSTANTS: + reverse_polish_notation += token + separator + elif token in ('True', 'False'): + reverse_polish_notation += token + separator + while stack: + reverse_polish_notation += stack.pop() + separator + return reverse_polish_notation + + +def quantity_of_arguments(function: str) -> int: + """From the description of the function determines how many arguments it includes + + :param function: mathematical or numerical functions + :return: quantity of arguments + + """ + spec = function.__doc__.split('\n')[0] + args = spec[spec.find('(') + 1:spec.find(')')] + return args.count(',') + 1 if args else 0 + + +def calculate(math_expression: str) -> Union[int, float, bool, tuple]: + reverse_polish_notation = transform_to_polish_notation(exec_isclose(check_expression(parse(math_expression)))) + stack = [0] + + def calculate_built_in_function(element: str): + if stack[-2] == ',': + op2 = stack.pop() + stack.pop() + op1 = stack.pop() + if stack[-1] == ',': + raise ValueError(f'invalid number of arguments') + else: + stack.append(BUILT_IN_FUNCTION[element](op1, int(op2))) + else: + op = stack.pop() + stack.append(BUILT_IN_FUNCTION[element](op)) + + def calculate_function_with_two_arg(element: str): + if stack[-2] == ',': + op2 = stack.pop() + stack.pop() + op1 = stack.pop() + if stack[-1] == ',': + raise ValueError(f'invalid number of arguments') + # ldexp is a function from the math module + elif element == 'ldexp': + stack.append(FUNCTIONS[element](op1, int(op2))) + # gcd is a function from the math module + elif element == 'gcd': + stack.append(FUNCTIONS[element](int(op1), int(op2))) + else: + stack.append(FUNCTIONS[element](op1, op2)) + else: + op = stack.pop() + stack.append(FUNCTIONS[element](op)) + + def calculate_single_arg_function(element: str): + # fsum is a function from the math module + if element == 'fsum': + numbers = [] + for index, number in enumerate(stack[::-1]): + if isinstance(number, (int, float)) and stack[::-1][1] == ',': + numbers.append(stack.pop()), stack.pop() + elif len(stack) > index + 2: + if isinstance(stack[::-1][index + 2], (int, float)) and stack[::-1][3] == ',': + numbers.append(stack.pop(-3)), stack.pop(), stack.pop(), stack.pop() + if stack[-1] == ':': + numbers.append(stack.pop(-3)), stack.pop(), stack.pop() + else: + numbers.append(stack.pop()) + stack.extend([numbers]) + op = stack.pop() + stack.append(FUNCTIONS[element](op)) + numbers.clear() + else: + op = stack.pop() + if stack[-1] == ',': + raise ValueError(f'invalid number of arguments') + stack.append(FUNCTIONS[element](op)) + + for token in reverse_polish_notation.split(' '): + if token in OPERATORS: + oper2, oper1 = stack.pop(), stack.pop() + stack.append(OPERATORS[token][0](oper1, oper2)) + elif token in BUILT_IN_FUNCTION: + calculate_built_in_function(token) + elif token in FUNCTIONS: + if quantity_of_arguments(FUNCTIONS[token]) == 1: + calculate_single_arg_function(token) + elif quantity_of_arguments(FUNCTIONS[token]) == 2: + calculate_function_with_two_arg(token) + elif token in [',', ':']: + stack.append(token) + elif token in CONSTANTS: + stack.append(CONSTANTS[token]) + elif token in ('True', 'False'): + stack.append(loads(token.lower())) + elif re.search(ALL_NUMBERS, token): + stack.append(float(token)) + if isinstance(stack[-1], bool): + return stack.pop() + elif isinstance(stack[-1], int): + return stack.pop() + elif isinstance(stack[-1], tuple): + return stack.pop() + else: + return stack.pop() + + +def main(): + try: + parser = argparse.ArgumentParser(description='Pure-python command-line calculator.') + parser.add_argument('EXPRESSION', type=str, help='expression string to evaluate') + args = parser.parse_args() + result = calculate(args.EXPRESSION) + print(result) + except Exception as exp: + print(f'ERROR: {exp}') + + +if __name__ == '__main__': + main() diff --git a/final_task/setup.py b/final_task/setup.py index e69de29b..f6caaca0 100644 --- a/final_task/setup.py +++ b/final_task/setup.py @@ -0,0 +1,10 @@ +from setuptools import setup, find_packages + +setup( + name='pycalc', + version='1.0', + packages=find_packages(), + description='Pure-python command-line calculator.', + py_modules=["pycalc", "tests"], + entry_points={'console_scripts': ['pycalc=pycalc:main']} + ) diff --git a/final_task/tests.py b/final_task/tests.py new file mode 100644 index 00000000..e4fc757c --- /dev/null +++ b/final_task/tests.py @@ -0,0 +1,179 @@ +import unittest +import math +import pycalc + + +class TestMyCaseCalculator(unittest.TestCase): + + def test_default_arithmetic(self): + self.assertEqual(pycalc.calculate('10+10'), 10+10) + self.assertEqual(pycalc.calculate('2*4'), 2*4) + self.assertEqual(pycalc.calculate('5-6'), 5-6) + self.assertEqual(pycalc.calculate('10/10'), 10/10) + self.assertEqual(pycalc.calculate('10^10'), 10**10) + self.assertEqual(pycalc.calculate('21//2'), 21//2) + self.assertEqual(pycalc.calculate('2%2'), 2 % 2) + + def test_with_floating_numbers(self): + self.assertEqual(pycalc.calculate('0.4 + 1.5'), 0.4 + 1.5) + self.assertEqual(pycalc.calculate('.4 + 1.5'), .4 + 1.5) + self.assertEqual(pycalc.calculate('.4^-(1.5+0.5)'), eval('.4**-(1.5+0.5)')) + + def test_number_theoretic_and_representation_functions(self): + self.assertEqual(pycalc.calculate('ceil(2.5)'), math.ceil(2.5)) + self.assertEqual(pycalc.calculate('copysign(1.0, -1.0)'), math.copysign(1.0, -1.0)) + self.assertEqual(pycalc.calculate('fabs(-5)'), math.fabs(-5)) + self.assertEqual(pycalc.calculate('factorial(2)'), math.factorial(2)) + self.assertEqual(pycalc.calculate('floor(5.5)'), math.floor(5.5)) + self.assertEqual(pycalc.calculate('fmod(5,5)'), math.fmod(5, 5)) + self.assertEqual(pycalc.calculate('frexp(5)'), math.frexp(5)) + self.assertEqual(pycalc.calculate('ldexp(3,10)'), math.ldexp(3, 10)) + self.assertEqual(pycalc.calculate('fsum([.1, .1, .1])'), math.fsum([.1, .1, .1])) + self.assertEqual(pycalc.calculate('fsum({1:2, 5:8, 6:120})'), math.fsum({1: 2, 5: 8, 6: 120})) + self.assertEqual(pycalc.calculate('gcd(5, 10)'), math.gcd(5, 10)) + self.assertEqual(pycalc.calculate('isclose(1, 2, rel_tol=0.05, abs_tol=0.0)'), + math.isclose(1, 2, rel_tol=0.05, abs_tol=0.0)) + self.assertEqual(pycalc.calculate('isclose(1, 2)'), + math.isclose(1, 2)) + self.assertEqual(pycalc.calculate('isclose(1, 2, rel_tol=0.05)'), + math.isclose(1, 2, rel_tol=0.05)) + self.assertEqual(pycalc.calculate('isfinite(3)'), math.isfinite(3)) + self.assertEqual(pycalc.calculate('isinf(3)'), math.isinf(3)) + self.assertEqual(pycalc.calculate('isnan(3)'), math.isnan(3)) + self.assertEqual(pycalc.calculate('modf(-3)'), math.modf(-3)) + self.assertEqual(pycalc.calculate('trunc(3.4)'), math.trunc(3.4)) + self.assertEqual(pycalc.calculate('exp(3)'), math.exp(3)) + self.assertEqual(pycalc.calculate('expm1(3)'), math.expm1(3)) + self.assertEqual(pycalc.calculate('log(10,2)'), math.log(10, 2)) + self.assertEqual(pycalc.calculate('log1p(10)'), math.log1p(10)) + self.assertEqual(pycalc.calculate('log10(10)'), math.log10(10)) + self.assertEqual(pycalc.calculate('log2(10)'), math.log2(10)) + self.assertEqual(pycalc.calculate('pow(2,3)'), math.pow(2, 3)) + self.assertEqual(pycalc.calculate('sqrt(25)'), math.sqrt(25)) + self.assertEqual(pycalc.calculate('erf(3)'), math.erf(3)) + self.assertEqual(pycalc.calculate('erfc(3)'), math.erfc(3)) + self.assertEqual(pycalc.calculate('gamma(3)'), math.gamma(3)) + self.assertEqual(pycalc.calculate('lgamma(3)'), math.lgamma(3)) + + def test_module_math_trigonometry(self): + self.assertEqual(pycalc.calculate('sin(90)'), math.sin(90)) + self.assertEqual(pycalc.calculate('cos(90)'), math.cos(90)) + self.assertEqual(pycalc.calculate('tan(90)'), math.tan(90)) + self.assertEqual(pycalc.calculate('asin(1)'), math.asin(1)) + self.assertEqual(pycalc.calculate('acos(0)'), math.acos(0)) + self.assertEqual(pycalc.calculate('atan(1)'), math.atan(1)) + self.assertEqual(pycalc.calculate('hypot(3,4)'), math.hypot(3, 4)) + self.assertEqual(pycalc.calculate('degrees(3.14)'), math.degrees(3.14)) + self.assertEqual(pycalc.calculate('radians(90)'), math.radians(90)) + self.assertEqual(pycalc.calculate('sinh(1)'), math.sinh(1)) + self.assertEqual(pycalc.calculate('cosh(1)'), math.cosh(1)) + self.assertEqual(pycalc.calculate('tanh(1)'), math.tanh(1)) + self.assertEqual(pycalc.calculate('asinh(1)'), math.asinh(1)) + self.assertEqual(pycalc.calculate('acosh(1)'), math.acosh(1)) + self.assertEqual(pycalc.calculate('atanh(0)'), math.atanh(0)) + self.assertEqual(pycalc.calculate('pi'), math.pi) + self.assertEqual(pycalc.calculate('e'), math.e) + self.assertEqual(pycalc.calculate('tau'), math.tau) + + def test_round_brackets(self): + self.assertEqual(pycalc.calculate('(2+2)*2'), (2+2)*2) + self.assertEqual(pycalc.calculate('(2+2)*2+(2+2)'), (2+2)*2+(2+2)) + self.assertEqual(pycalc.calculate('2+(2+(2+3)+3)+2'), 2+(2+(2+3)+3)+2) + self.assertEqual(pycalc.calculate('2+(2+3)*3+2'), 2+(2+3)*3+2) + self.assertEqual(pycalc.calculate('((2+2)*3)+2'), ((2+2)*3)+2) + + +class TestEpamCaseCalculator(unittest.TestCase): + + def test_unary_operators(self): + self.assertEqual(pycalc.calculate("-13"), -13) + self.assertEqual(pycalc.calculate("6-(-13)"), 6-(-13)) + self.assertEqual(pycalc.calculate("1---1"), 1---1) + self.assertEqual(pycalc.calculate("-+---+-1"), -+---+-1) + + def test_operation_priority(self): + self.assertEqual(pycalc.calculate("1+2*2"), 1+2*2) + self.assertEqual(pycalc.calculate("1+(2+3*2)*3"), 1+(2+3*2)*3) + self.assertEqual(pycalc.calculate("10*(2+1)"), 10*(2+1)) + self.assertEqual(pycalc.calculate("10^(2+1)"), 10**(2+1)) + self.assertEqual(pycalc.calculate("100/3^2"), 100/3**2) + self.assertEqual(pycalc.calculate("100/3%2^2"), 100/3 % 2**2) + + def test_functions_and_constants(self): + self.assertEqual(pycalc.calculate("pi+e"), math.pi+math.e) + self.assertEqual(pycalc.calculate("log(e)"), math.log(math.e)) + self.assertEqual(pycalc.calculate("sin(pi/2)"), math.sin(math.pi/2)) + self.assertEqual(pycalc.calculate("log10(100)"), math.log10(100)) + self.assertEqual(pycalc.calculate("sin(pi/2)*111*6"), math.sin(math.pi/2)*111*6) + self.assertEqual(pycalc.calculate("2*sin(pi/2)"), 2*math.sin(math.pi/2)) + self.assertEqual(pycalc.calculate("pow(2, 3)"), math.pow(2, 3)) + self.assertEqual(pycalc.calculate("abs(-5)"), abs(-5)) + self.assertEqual(pycalc.calculate("round(123.4567890)"), round(123.4567890)) + self.assertEqual(pycalc.calculate("round(123.4567890,2)"), round(123.4567890, 2)) + + def test_associative(self): + self.assertEqual(pycalc.calculate("102%12%7"), 102 % 12 % 7) + self.assertEqual(pycalc.calculate("100/4/3"), 100/4/3) + self.assertEqual(pycalc.calculate("2^3^4"), 2**3**4) + + def test_comparison_operators(self): + self.assertEqual(pycalc.calculate("1+2*3==1+2*3"), 1+2*3 == 1+2*3) + self.assertAlmostEqual(pycalc.calculate("e^5>=e^5+1"), math.e**5 >= math.e**5+1) + self.assertAlmostEqual(pycalc.calculate("1+2*4/3+1!=1+2*4/3+2"), 1+2*4/3+1 != 1+2*4/3+2) + self.assertAlmostEqual(pycalc.calculate("True+1"), True + 1) + + def test_common_tests(self): + self.assertEqual(pycalc.calculate("(100)"), eval("(100)")) + self.assertEqual(pycalc.calculate("666"), 666) + self.assertEqual(pycalc.calculate("-.1"), -.1) + self.assertEqual(pycalc.calculate("1/3"), 1/3) + self.assertEqual(pycalc.calculate("1.0/3.0"), 1.0/3.0) + self.assertEqual(pycalc.calculate(".1 * 2.0^56.0"), .1 * 2.0**56.0) + self.assertEqual(pycalc.calculate("e^34"), math.e**34) + self.assertEqual(pycalc.calculate("(2.0^(pi/pi+e/e+2.0^0.0))"), + (2.0**(math.pi/math.pi+math.e/math.e+2.0**0.0))) + self.assertEqual(pycalc.calculate("(2.0^(pi/pi+e/e+2.0^0.0))^(1.0/3.0)"), + (2.0**(math.pi/math.pi+math.e/math.e+2.0**0.0))**(1.0/3.0)) + self.assertEqual(pycalc.calculate("sin(pi/2^1) + log(1*4+2^2+1, 3^2)"), + math.sin(math.pi/2**1) + math.log(1*4+2**2+1, 3**2)) + self.assertEqual(pycalc.calculate("10*e^0*log10(.4 -5/ -0.1-10) - -abs(-53/10) + -5"), + 10*math.e**0*math.log10(.4 - 5 / -0.1-10) - -abs(-53/10) + -5) + expression = "sin(-cos(-sin(3.0)-cos(-sin(-3.0*5.0)-sin(cos(log10(43.0))))+" \ + "cos(sin(sin(34.0-2.0^2.0))))--cos(1.0)--cos(0.0)^3.0)" + self.assertEqual(pycalc.calculate(expression), + math.sin(-math.cos(-math.sin(3.0)-math.cos(-math.sin(-3.0*5.0) - + math.sin(math.cos(math.log10(43.0)))) + + math.cos(math.sin(math.sin(34.0-2.0**2.0))))--math.cos(1.0) - + -math.cos(0.0)**3.0)) + self.assertEqual(pycalc.calculate("2.0^(2.0^2.0*2.0^2.0)"), 2.0**(2.0**2.0*2.0**2.0)) + self.assertEqual(pycalc.calculate("sin(e^log(e^e^sin(23.0),45.0) + cos(3.0+log10(e^-e)))"), + math.sin(math.e**math.log(math.e**math.e**math.sin(23.0), 45.0) + + math.cos(3.0+math.log10(math.e**-math.e)))) + + def test_Error_cases(self): + self.assertRaises(ValueError, pycalc.calculate, "") + self.assertRaises(ValueError, pycalc.calculate, "+") + self.assertRaises(ValueError, pycalc.calculate, "1-") + self.assertRaises(ValueError, pycalc.calculate, "1 2") + self.assertRaises(ValueError, pycalc.calculate, "==7") + self.assertRaises(ValueError, pycalc.calculate, "1 + 2(3 * 4))") + self.assertRaises(ValueError, pycalc.calculate, "((1+2)") + self.assertRaises(ValueError, pycalc.calculate, "1 + 1 2 3 4 5 6 ") + self.assertRaises(ValueError, pycalc.calculate, "log100(100)") + self.assertRaises(ValueError, pycalc.calculate, "------") + self.assertRaises(ValueError, pycalc.calculate, "5 > = 6") + self.assertRaises(ValueError, pycalc.calculate, "5 / / 6") + self.assertRaises(ValueError, pycalc.calculate, "6 < = 6") + self.assertRaises(ValueError, pycalc.calculate, "6 * * 6") + self.assertRaises(ValueError, pycalc.calculate, "(((((") + self.assertRaises(ValueError, pycalc.calculate, "abs") + self.assertRaises(ValueError, pycalc.calculate, "abs+1") + self.assertRaises(ValueError, pycalc.calculate, "isclose(1)") + self.assertRaises(ValueError, pycalc.calculate, "cos(2,1)") + self.assertRaises(ValueError, pycalc.calculate, "2**2") + self.assertRaises(ValueError, pycalc.calculate, "pow(2, 3, 4)") + self.assertRaises(ValueError, pycalc.calculate, "fsum[1,,2,3]") + + +if __name__ == '__main__': + unittest.main()