diff --git a/__init__.py b/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/final_task/pycalc/__init__.py b/final_task/pycalc/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/final_task/pycalc/argument_parser.py b/final_task/pycalc/argument_parser.py new file mode 100644 index 00000000..0b0a7878 --- /dev/null +++ b/final_task/pycalc/argument_parser.py @@ -0,0 +1,37 @@ +"""Argument parser module""" + +import argparse +from collections import namedtuple +from .operator_manager import create_func_dict, find_user_functions + + +def arg_parser(): + """ + This function gather positional arguments from users, + create a function_dict with users and built_in math functions if there is users_modules, + otherwise create a function_dict only with built_in math functions + :return: line as namedtuple(expression, function_dict) + """ + parser = argparse.ArgumentParser( + description='Pure-python command-line calculator.', + prog='pycalc' + ) + parser.add_argument( + '-m', + '--use-modules', + help='additional modules to use', + metavar='MODULE [MODULE ...]' + ) + parser.add_argument('EXPRESSION', help='expression string to evaluate') + args = parser.parse_args() + expression = args.EXPRESSION + if args.use_modules: + user_functions = find_user_functions(args.use_modules) + function_dict = create_func_dict(user_functions) + expression_line = namedtuple('expression_line', 'expression functions') + line = expression_line(expression, function_dict) + else: + function_dict = create_func_dict() + expression_line = namedtuple('expression_line', 'expression functions') + line = expression_line(expression, function_dict) + return line diff --git a/final_task/pycalc/calculator.py b/final_task/pycalc/calculator.py new file mode 100644 index 00000000..fe5420f0 --- /dev/null +++ b/final_task/pycalc/calculator.py @@ -0,0 +1,118 @@ +"""Calculator module""" + +from .stack_manager import Stack +from .operator_manager import operator_dict, unary_dict +from .split_operators import SplitOperators +from .converter import Converter + + +class Calculator: + """Calculator class""" + def __init__(self, expression, functions): + """ + Generates an instance of the Calculator class, + take an expression_line from user, + create an instance of the SplitOperators, + create an instance of the Converter, + create a Stack to put all operands, + create a Stack to put all operations + self.current_operator need to check the priority of current_operator + and the operator on stack after one calculation + self.arg_result_lst need to put calculated arguments and to make a tuple + of arguments + """ + self.expression_line = expression + self.function_dict = functions + self.parser = SplitOperators(self.expression_line, self.function_dict).split_operators() + self.converted_list = Converter(self.parser, self.function_dict).converter() + self.current_result = "" + self.operands = Stack() + self.function = Stack() + self.current_operator = "" + self.arg_result_lst = [] + + def _calc_on_stack(self): + """ + Encapsulate function + Takes an item from function_stack and operands from the operands_stack + and performs calculation. + Try to send arguments to math function as variables, + or if it impossible send an iterable (e.g. for math.fsum), + or Raise an Exception if there is two many arguments for current function + If it possible to make next calculation - make recursion + Returns result of calculation to the current_result. + """ + operator_on_stack = self.function.take_from_stack() + if operator_on_stack in self.function_dict.values(): + func_args = self.operands.take_from_stack() + try: + self.current_result = operator_on_stack['operator'](*func_args) + except TypeError: + try: + self.current_result = operator_on_stack['operator'](func_args) + except TypeError as err: + raise SyntaxError(err) + elif operator_on_stack in operator_dict.values() or operator_on_stack in unary_dict.values(): + if len(self.operands.stack) == 1: + second_operand = self.operands.take_from_stack() + first_operand = 0 + else: + second_operand = self.operands.take_from_stack() + first_operand = self.operands.take_from_stack() + self.current_result = operator_on_stack['operator'](first_operand, second_operand) + self.operands.put_on_stack(self.current_result) + if self.function.stack and self.function.top() is not '(': + if self.current_operator['priority'] >= self.function.top()['priority']: + self.current_result = self._calc_on_stack() + return self.current_result + + def calculate(self): + """ + For each item in converted_list using Reverse Polish notation, method + check a type and put it on either operands or function stack, + and invokes calc_on_stack method to perform calculation itself. + If item is tuple (function arguments) generate a new instance of + Calculator to calculate it and after that put results as tuple to + the operand stack. After calculating current arguments arg_result_lst become + an empty list. Returns result of calculation received from calc_on_stack method + """ + for item in self.converted_list: + if isinstance(item, tuple): + for argument in item: + arg_calculate = Calculator(argument, self.function_dict) + arg_result = arg_calculate.calculate() + self.arg_result_lst.append(arg_result) + self.operands.put_on_stack(tuple(self.arg_result_lst)) + self.arg_result_lst = [] + elif isinstance(item, float) or isinstance(item, int): + self.operands.put_on_stack(item) + elif item in operator_dict.values() \ + or item in self.function_dict.values() \ + or item in unary_dict.values(): + self.current_operator = item + if self.function.is_empty(): + self.function.put_on_stack(self.current_operator) + else: + if self.function.top() is '(' \ + or self.current_operator['priority'] < self.function.top()['priority'] \ + or self.current_operator == operator_dict['^'] \ + and self.function.top() == operator_dict['^']: + self.function.put_on_stack(self.current_operator) + else: + self._calc_on_stack() + self.function.put_on_stack(self.current_operator) + elif item is '(': + self.function.put_on_stack(item) + elif item is ')' and self.function.top() == '(': + self.function.take_from_stack() + else: + for i in self.function.stack: + self._calc_on_stack() + if item is ')' and self.function.stack: + if self.function.top() is '(': + self.function.take_from_stack() + break + while self.function.stack: + self._calc_on_stack() + self.current_result = self.operands.take_from_stack() + return self.current_result diff --git a/final_task/pycalc/check_manager.py b/final_task/pycalc/check_manager.py new file mode 100644 index 00000000..14523797 --- /dev/null +++ b/final_task/pycalc/check_manager.py @@ -0,0 +1,93 @@ +"""Check manager module""" + +import sys +from .operator_manager import operator_dict + + +def check_expression(expression_line): + """ + Check if the expression_line is not empty and the brackets are correct, + otherwise raise an Exception + :param expression_line: string from the user + :return: clear expression line as str + """ + if not expression_line: + raise SyntaxError('Expression cannot be empty') + if expression_line.count('(') < expression_line.count(')'): + raise SyntaxError('Opening bracket required!') + elif expression_line.count('(') > expression_line.count(')'): + raise SyntaxError('Closing bracket required!') + return expression_line + + +def check_parsing_list(parsing_list, function_dict): + """ + Check if the parsing list is valid otherwise raise an Exception for next reasons: + - expression starts with math operators (except "+" and "-") + - there is no operand in the expression line + - expression line ends with math operator or math function + :param parsing_list: list from instance of SplitOperators class + :param function_dict: dict with all functions {'operator': function, 'priority': 0} + :return: clear parsing_list as str + """ + if parsing_list[0] in operator_dict.keys(): + if parsing_list[0] is not '+' and parsing_list[0] is not '-': + raise SyntaxError('Expression cannot start with "{}"'.format(parsing_list[0])) + if len(parsing_list) == 1: + if isinstance(parsing_list[0], int) or isinstance(parsing_list[0], float): + return parsing_list + raise SyntaxError('Expression must include at list one operand!') + if parsing_list[-1] in operator_dict.keys(): + raise SyntaxError('Extra operator "{}" at the end of an expression!'.format(parsing_list[-1])) + if parsing_list[-1] in function_dict.keys(): + raise SyntaxError('Function "{}" without argument'.format(parsing_list[-1])) + return parsing_list + + +def operator_check(operator_symbol): + """ + Check if there is an operator with this symbol in the operators_dict + :param operator_symbol: last_symbol (str) from instance of SplitOperators class + :return: clear operator_symbol as str + """ + if operator_symbol in operator_dict.keys(): + return operator_symbol + raise SyntaxError('Typo in math operator!') + + +def number_check(number): + """ + Check if number is int or float + :param number: last_number (str) from instance of SplitOperators class + :return: number as int or float + """ + try: + return int(number) + except ValueError: + return float(number) + + +def function_check(function_name, function_dict): + """ + Check if function_name is a key in function_dict. + Check the python version to add constant "tau". + If function_name is "pi", "e", "tau", "inf" or "nan" convert it into float + If there is no such name in function_dict Raise an Exception + :param function_name: last_letter (str) from instance of SplitOperators class + :param function_dict: dict with all functions {'operator': function, 'priority': 0} + :return: float or clear function_name as str + """ + if function_name == 'tau': + if sys.version_info >= (3, 6): + return function_dict[function_name]['operator'] + else: + return 2 * function_dict['e']['operator'] + elif function_name in function_dict.keys(): + if isinstance(function_dict[function_name]['operator'], int) \ + or isinstance(function_dict[function_name]['operator'], float): + return function_dict[function_name]['operator'] + return function_name + else: + raise SyntaxError( + 'There is no function with this name {}!'.format(function_name) + ) diff --git a/final_task/pycalc/converter.py b/final_task/pycalc/converter.py new file mode 100644 index 00000000..603d176c --- /dev/null +++ b/final_task/pycalc/converter.py @@ -0,0 +1,133 @@ +"""Converter module""" + +from .operator_manager import operator_dict, unary_dict +from .check_manager import check_parsing_list + + +class Converter: + """Converter class""" + def __init__(self, parsing_list, functions): + """ + Generates an instance of the Converter class, + take an parsing_list as list from instance of SplitOperators class + create an empty list to put the result of converting + self.last_item is a place to put all successive "+" and "-" + and after appending any other element into self.converted_list + self.last_item become an empty str + """ + self.parsing_list = parsing_list + self.function_dict = functions + self.last_item = "" + self.converted_list = [] + + def _clean_add_sub_operators(self): + """ + Encapsulate function + Take all the successive of "+" and "-" (self.last)item) + and return "-" if count("-") is odd or "+" if count is even + """ + if self.last_item.count('-') % 2 == 0: + if self.converted_list[-1] == '(': + self.last_item = "" + else: + self.last_item = '+' + else: + self.last_item = '-' + + def _append_to_converted_list(self, *args): + """ + Encapsulate function + Append all converted elements into self.converted_list + and update successive of "+" and "-" to empty value + """ + [self.converted_list.append(i) for i in args] + self.last_item = "" + + def _number_converter(self, number): + """ + Encapsulate function + Add unary operator if number is the first element in the self.parsing_list, + add "-" to number if self.last_symbol is "-", otherwise add "+" + And if there is no self.last_symbol + :param number: is int or float object from self.parsing_list + """ + if self.last_item == '-': + if self.converted_list[-1] == 0: + self._append_to_converted_list(unary_dict[self.last_item], number) + elif self.last_item == '-' and self.converted_list[-1] != '(' \ + and self.converted_list[-1] not in operator_dict.values(): + self._append_to_converted_list(operator_dict[self.last_item], number) + else: + self._append_to_converted_list(-number) + else: + self._append_to_converted_list(number) + + def _operator_converter(self, operator_str): + """ + Encapsulate function + Convert math operator symbol into dictionary with built-in math operator + Raise an exception if there is no operand between two math operators + :param operator_str: is one math symbol from self.parsing_list + """ + if operator_str == '-' or operator_str == '+': + self.last_item += operator_str + else: + try: + if self.converted_list[-1]['operator']: + raise SyntaxError('Missing operand between two math operators!') + except TypeError: + self._append_to_converted_list(operator_dict[operator_str]) + + def _function_converter(self, function): + """ + Encapsulate function + Convert math function name into dictionary with built-in math function + Check necessary of "-" before the math function + :param function_str: is math function name from self.parsing_list + """ + if self.last_item: + if self.last_item == '-' and self.converted_list[-1] != '(': + self._append_to_converted_list( + operator_dict['+'], + -1, + operator_dict['*'], + self.function_dict[function] + ) + else: + self._append_to_converted_list( + -1, + operator_dict['*'], + self.function_dict[function] + ) + else: + self._append_to_converted_list(self.function_dict[function]) + + def converter(self): + """ + The main function of the Converter class + Call function to check if the parsing_list is valid + Go thought the parsing_list and call necessary encapsulate function + for every number, operator or function + :return: converted_list + """ + check_parsing_list(self.parsing_list, self.function_dict) + if self.parsing_list[0] in operator_dict.keys(): # parsing_list[0] == '+' or '-' + self.converted_list.append(0) + for i in self.parsing_list: + if i == " ": + continue + if i != '-' and i != '+' and self.last_item: + self._clean_add_sub_operators() + if self.last_item == '+': + self._append_to_converted_list(operator_dict[self.last_item]) + if isinstance(i, float) or isinstance(i, int): + self._number_converter(i) + elif i in operator_dict.keys(): + self._operator_converter(i) + elif i in self.function_dict.keys(): + self._function_converter(i) + else: + if self.last_item: + self._append_to_converted_list(operator_dict['-']) + self._append_to_converted_list(i) + return self.converted_list diff --git a/final_task/pycalc/operator_manager.py b/final_task/pycalc/operator_manager.py new file mode 100644 index 00000000..96c87c63 --- /dev/null +++ b/final_task/pycalc/operator_manager.py @@ -0,0 +1,62 @@ +"""Operators manager module""" + +import math +import operator +import importlib + + +def create_func_dict(func_name=None): + """ + Returns dictionary where keys are the name of functions and constants from module math, + and values are a dictionary {'operator': , 'priority': number} + """ + func_dict = { + 'abs': {'operator': abs, 'priority': 0}, + 'round': {'operator': round, 'priority': 0} + } + for key, value in math.__dict__.items(): + if key.startswith('_'): + continue + func_dict[key] = {'operator': value, 'priority': 0} + if func_name: + for key, value in func_name.items(): + func_dict[key] = {'operator': value, 'priority': 0} + return func_dict + + +def find_user_functions(module): + """ + Create a dict of functions and constants from user module, + if user module and pycaclc is located in the one package + :param module: name of the user module (optional argument in argparser) + :return: dict {function_name: function} + """ + try: + user_module = importlib.import_module('{}'.format(module)) + item = [i for i in dir(user_module) if not i.startswith('_')] + user_functions = {i: user_module.__dict__[i] for i in item} + return user_functions + except ImportError: + raise SyntaxError('There is no module with name {}'.format(module)) + + +operator_dict = { + '+': {'operator': operator.add, 'priority': 4}, + '-': {'operator': operator.sub, 'priority': 4}, + '/': {'operator': operator.truediv, 'priority': 3}, + '*': {'operator': operator.mul, 'priority': 3}, + '%': {'operator': operator.mod, 'priority': 3}, + '//': {'operator': operator.floordiv, 'priority': 3}, + '^': {'operator': operator.pow, 'priority': 1}, + '**': {'operator': operator.pow, 'priority': 1}, + '==': {'operator': operator.eq, 'priority': 5}, + '!=': {'operator': operator.ne, 'priority': 5}, + '>': {'operator': operator.gt, 'priority': 5}, + '<': {'operator': operator.lt, 'priority': 5}, + '>=': {'operator': operator.ge, 'priority': 5}, + '<=': {'operator': operator.le, 'priority': 5}, +} + +unary_dict = { + '-': {'operator': operator.sub, 'priority': 2} +} diff --git a/final_task/pycalc/pycalc.py b/final_task/pycalc/pycalc.py new file mode 100644 index 00000000..fdbe7742 --- /dev/null +++ b/final_task/pycalc/pycalc.py @@ -0,0 +1,30 @@ +#!/usr/bin/python3 +"""pycalc module""" + +from .calculator import Calculator +from .argument_parser import arg_parser + + +def main(): + """ + This function take expression_line from users and invokes calculation. Wrapped in try block to catch error + messages while preparing expression and calculating itself + :return: result of calculation of the users expression + """ + try: + expression_line = arg_parser() + calc = Calculator(expression_line.expression, expression_line.functions) + result = calc.calculate() + print(result) + except SyntaxError as err: + print('ERROR: {}'.format(err)) + except ZeroDivisionError as err: + print('ERROR: {}!'.format(err)) + except ValueError as err: + print('ERROR: {}!'.format(err)) + except OverflowError as err: + print('ERROR: {}!'.format(err)) + + +if __name__ == '__main__': + main() diff --git a/final_task/pycalc/split_operators.py b/final_task/pycalc/split_operators.py new file mode 100644 index 00000000..b75f0304 --- /dev/null +++ b/final_task/pycalc/split_operators.py @@ -0,0 +1,200 @@ +"""Split operators module""" + +from .check_manager import check_expression, number_check, operator_check, function_check + + +class SplitOperators: + """SplitOperators class""" + def __init__(self, expression, functions): + """ + Generates an instance of the SplitManager class, + take an expression line as string, + create an empty list to put the result of parsing line + self.last_number is a variable to generate number from sequence of numbers + self.last_letter is a variable to generate function name from sequence of letters + self.last_symbol is a variable to generate math operator from one or sequence of symbols + self.blank_item is changed into True when we found whitespace + self.brackets is a place to put all brackets to check if the positions of brackets is right + self.arguments_needs is changed into True when we have a math function, + and return into False when we have collected all the arguments for current function + self.function_arguments is a place to put all arguments for current function, + and it become an empty str again when we have collected all the arguments for current function + self.brackets_in_arguments is a place to all brackets inside arguments + to check when the last element of arguments was added + """ + self.expression_line = expression + self.function_dict = functions + self.parsing_list = [] + self.last_number = "" + self.last_letter = "" + self.last_symbol = "" + self.blank_item = False + self.brackets = "" + self.arguments_needs = False + self.function_arguments = "" + self.brackets_in_arguments = "" + + def _append_to_parsing_list(self): + """ + Encapsulate function + Check if there is a number, symbol of the math operator + or the name of the math function; call the check method of each element + and if the element is clear append it to the self.parsing_list. + Change the value of self.arguments_needs if math function was added + """ + if self.last_symbol: + self.parsing_list.append(operator_check(self.last_symbol)) + self.last_symbol = "" + elif self.last_number: + self.parsing_list.append(number_check(self.last_number)) + self.last_number = "" + elif self.last_letter: + self.parsing_list.append(function_check(self.last_letter, self.function_dict)) + if self.parsing_list[-1] in self.function_dict.keys(): + self.arguments_needs = True + self.last_letter = "" + + def _number_parser(self, number): + """ + Encapsulate function + Create a number from successive digits in expression line, + raise Exception when there is a space between two digits, + or when there is more than two comma. If there is symbol of math operator + or the name of math function call function to put this item into self.parsing_list + :param number: is one digit from expression_line + """ + if self.blank_item and not isinstance(self.parsing_list[-1], str): + raise SyntaxError('Blank symbol between two operands!') + elif self.blank_item: + self.blank_item = False + if self.last_symbol: + self._append_to_parsing_list() + if self.last_letter: + self.last_letter += number + else: + if '.' in self.last_number and number == '.': + raise SyntaxError('Typo in the operand (two comma)!') + else: + self.last_number += number + + def _function_parser(self, letter): + """ + Encapsulate function + Create a name of function from the successive letters. + If there is symbol of math operator call function + to put this item into self.parsing_list + :param letter: is one letter from expression_line + """ + if self.last_symbol: + self._append_to_parsing_list() + self.last_letter += letter + + def _twice_operator_parser(self, symbol): + """ + Encapsulate function + Create a twice operator symbol from successive math symbols. + Raise an Exception when there is space between two symbols, + or if it try to create math operator with more than two elements + If there is symbol of math operator call function + to put this item into self.parsing_list + :param symbol: is one math symbol from expression_line + """ + if len(self.last_symbol) == 2: + raise SyntaxError('Invalid operator "{}"'.format(self.last_symbol + symbol)) + if self.blank_item and str(self.parsing_list[-1]) in "!=<>/*": + raise SyntaxError('Blank symbol between twice operator') + elif self.blank_item: + self.blank_item = False + if not self.last_symbol: + self._append_to_parsing_list() + self.last_symbol += symbol + + def _simple_operator_bracket_parser(self, symbol): + """ + Encapsulate function + At fist call function to put data into self.parsing_list if it necessary. + After this it put current symbol to the self.parsing_list. + If the first bracket is closed Raise an Exception. + If we need to collect function arguments it add bracket to the self.brackets_in_arguments + :param symbol: is one math symbol from expression_line or bracket or comma + """ + self._append_to_parsing_list() + if symbol in '()': + self.brackets += symbol + if self.brackets.count(')') > self.brackets.count('('): + raise SyntaxError('Position of brackets is wrong') + elif self.arguments_needs: + self.brackets_in_arguments += symbol + else: + self.parsing_list.append(symbol) + + elif symbol != ' ': + self.parsing_list.append(symbol) + + def _collect_function_arguments(self, item): + """ + Encapsulate function + Collect all the arguments for last function in the parsing list. + If the math function have more than one argument it will make + separations of the arguments by "," and check if this split was correct + or it should be separated by comma after (at first calculate one by one arguments). + If argument is an expression with embedded functions and arguments (nesting doll) + this function will add it like a string, that will be calculated after in recursion way. + All arguments are strings in tuple. + When the last item of the arguments was added it changed self.arguments_need to False, + returns the self.function_arguments and self.brackets_in_the_arguments to empty stings + and last bracket will be added to self.brackets + :param item: is one by one item from expression_line, when we start + to collect function_arguments + """ + if item in '()': + self.brackets_in_arguments += item + if self.brackets_in_arguments.count('(') == self.brackets_in_arguments.count(')'): + self.brackets += item + current_arguments = tuple(self.function_arguments.split(',')) + need_to_split_arguments = 0 + for i in current_arguments: + if i.count('(') == i.count(')'): + need_to_split_arguments += 1 + if need_to_split_arguments: + self.parsing_list.append(tuple(current_arguments)) + else: + self.parsing_list.append(tuple([self.function_arguments])) + self.arguments_needs = False + self.function_arguments = "" + self.brackets_in_arguments = "" + else: + self.function_arguments += item + else: + self.function_arguments += item + + def split_operators(self): + """ + The main function of class SplitOperators + Go thought the expression_line and call necessary encapsulate function + for every digit, letter, symbol or to collect function_arguments + Raise an Exception when there is a twice operator in the end of line + :return: parsing_list as a list where each element is operand, math symbol, + name of math function or a tuple of function arguments + """ + if check_expression(self.expression_line): + for i in self.expression_line: + if i == " ": + self.blank_item = True + self._append_to_parsing_list() + elif self.arguments_needs: + self._collect_function_arguments(i) + elif i.isnumeric() or i == '.': + self._number_parser(i) + elif i.isalpha(): + self._function_parser(i) + elif i in "!=<>/*": + self._twice_operator_parser(i) + else: + self._simple_operator_bracket_parser(i) + if self.last_symbol: + raise SyntaxError( + 'Extra operator "{}" at the end of the expression!'.format(self.last_symbol) + ) + self._append_to_parsing_list() + return self.parsing_list diff --git a/final_task/pycalc/stack_manager.py b/final_task/pycalc/stack_manager.py new file mode 100644 index 00000000..0ecf270f --- /dev/null +++ b/final_task/pycalc/stack_manager.py @@ -0,0 +1,37 @@ +"""Stack module""" + + +class Stack: + """Stack class""" + def __init__(self): + """ + Generates an instance of the Stack object + """ + self.stack = list() + + def put_on_stack(self, item): + """ + Appends an item to the container + """ + self.stack.append(item) + + def top(self): + """ + Returns a last item from stack + """ + return self.stack[-1] + + def take_from_stack(self): + """ + Remove and returns a last item from stack + """ + return self.stack.pop() + + def is_empty(self): + """ + Returns True if no items on a stack, otherwise returns False + """ + if len(self.stack) == 0: + return True + else: + return False diff --git a/final_task/setup.py b/final_task/setup.py index e69de29b..0449ea78 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='1.0', + author='Mikhail Sauchuk', + author_email='mishasavchuk@gmail.com', + packages=find_packages(), + entry_points={'console_scripts': ['pycalc = pycalc.pycalc:main']}, + description='Pure-python command-line calculator.', + platforms='any', + py_modules=['pycalc'] +) diff --git a/final_task/tests/__init__.py b/final_task/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/final_task/tests/test_calculator.py b/final_task/tests/test_calculator.py new file mode 100644 index 00000000..78600a38 --- /dev/null +++ b/final_task/tests/test_calculator.py @@ -0,0 +1,136 @@ +import unittest +import math +import operator +from pycalc.calculator import Calculator +from pycalc.operator_manager import create_func_dict + +function_dict = create_func_dict() + + +class TestCalculator(unittest.TestCase): + + def test_init_class(self): + calc = Calculator('42+pi', function_dict) + self.assertEqual('42+pi', calc.expression_line) + self.assertEqual(function_dict, calc.function_dict) + self.assertEqual([42, '+', math.pi], calc.parser) + self.assertEqual([42, {'operator': operator.add, 'priority': 4}, math.pi], + calc.converted_list) + self.assertEqual("", calc.current_result) + self.assertTrue(calc.operands.is_empty()) + self.assertTrue(calc.function.is_empty()) + self.assertEqual("", calc.current_operator) + self.assertEqual([], calc.arg_result_lst) + + def test_calc_on_stack_function_with_one_argument(self): + calc = Calculator('sin(pi/2)', function_dict) + calc.function.put_on_stack({'operator': math.sin, 'priority': 0}) + calc.operands.put_on_stack((math.pi/2,)) + calc._calc_on_stack() + self.assertEqual(1.0, calc.current_result) + + def test_calc_on_stack_function_with_two_arguments(self): + calc = Calculator('log(16,4)', function_dict) + calc.function.put_on_stack({'operator': math.log, 'priority': 0}) + calc.operands.put_on_stack((16, 4)) + calc._calc_on_stack() + self.assertEqual(2.0, calc.current_result) + + def test_calc_on_stack_function_with_iterable_argument(self): + calc = Calculator('fsum([1,2,3,4])', function_dict) + calc.function.put_on_stack({'operator': math.fsum, 'priority': 0}) + calc.operands.put_on_stack([1, 2, 3, 4]) + calc._calc_on_stack() + self.assertEqual(10.0, calc.current_result) + + def test_calculate_too_many_arguments(self): + calc = Calculator('sin(pi,42)', function_dict) + calc.function.put_on_stack({'operator': math.sin, 'priority': 0}) + calc.operands.put_on_stack((math.pi, 42)) + with self.assertRaises(SyntaxError): + calc.calculate() + + def test_calc_on_stack_operator_with_two_operands(self): + calc = Calculator('42/7', function_dict) + calc.function.put_on_stack({'operator': operator.truediv, 'priority': 3}) + calc.operands.put_on_stack(42) + calc.operands.put_on_stack(7) + calc._calc_on_stack() + self.assertEqual(6, calc.current_result) + + def test_calc_on_stack_operator_with_one_operand(self): + calc = Calculator('-42', function_dict) + calc.function.put_on_stack({'operator': operator.sub, 'priority': 4}) + calc.operands.put_on_stack(42) + calc._calc_on_stack() + self.assertEqual(-42, calc.current_result) + + def test_calc_on_stack_unary_operator(self): + calc = Calculator('-42', function_dict) + calc.function.put_on_stack({'operator': operator.sub, 'priority': 2}) + calc.operands.put_on_stack(42) + calc._calc_on_stack() + self.assertEqual(-42, calc.current_result) + + def test_clack_on_stack_recursion_function(self): + calc = Calculator('3*2^2+1', function_dict) + calc.function.stack = [{'operator': operator.mul, 'priority': 3}, + {'operator': operator.pow, 'priority': 1}] + calc.operands.stack = [3, 2, 2] + calc.current_operator = {'operator': operator.add, 'priority': 4} + calc._calc_on_stack() + self.assertEqual(12, calc.current_result) + + def test_calculate_one_operand_int(self): + calc = Calculator('42', function_dict) + calc.converted_list = [42] + self.assertEqual(42, calc.calculate()) + + def test_calculate_one_operand_float(self): + calc = Calculator('42.42', function_dict) + calc.converted_list = [42.42] + self.assertEqual(42.42, calc.calculate()) + + def test_calculate_math_operator(self): + calc = Calculator('42+8', function_dict) + calc.converted_list = [42, {'operator': operator.add, 'priority': 4}, 8] + self.assertEqual(50, calc.calculate()) + + def test_calculate_unary_operator(self): + calc = Calculator('42-8', function_dict) + calc.converted_list = [42, {'operator': operator.sub, 'priority': 2}, 8] + self.assertEqual(34, calc.calculate()) + + def test_calculate_ad_operator_after_bracket(self): + calc = Calculator('(42-8)/2', function_dict) + calc.converted_list = ['(', 42, {'operator': operator.sub, 'priority': 4}, 8, ')', + {'operator': operator.truediv, 'priority': 3}, 2] + self.assertEqual(17, calc.calculate()) + + def test_calculate_ad_operator_with_higher_class_priority(self): + calc = Calculator('42-8/2', function_dict) + calc.converted_list = [42, {'operator': operator.sub, 'priority': 4}, 8, + {'operator': operator.truediv, 'priority': 3}, 2] + self.assertEqual(38, calc.calculate()) + + def test_calculate_ad_operator_power(self): + calc = Calculator('42-8^2', function_dict) + calc.converted_list = [42, {'operator': operator.sub, 'priority': 4}, 8, + {'operator': operator.pow, 'priority': 1}, 2] + self.assertEqual(-22, calc.calculate()) + + def test_calculate_ad_operator_power_after_power(self): + calc = Calculator('2^4^2', function_dict) + calc.converted_list = [2, {'operator': operator.pow, 'priority': 1}, 4, + {'operator': operator.pow, 'priority': 1}, 2] + self.assertEqual(65536, calc.calculate()) + + def test_calculate_expression_inside_brackets(self): + calc = Calculator('1+(1-4/2)*3', function_dict) + calc.converted_list = [ + 1, {'operator': operator.add, 'priority': 4}, '(', 1, + {'operator': operator.sub, 'priority': 4}, 4, + {'operator': operator.truediv, 'priority': 3}, 2, ')', + {'operator': operator.mul, 'priority': 3}, 3 + ] + self.assertEqual(-2, calc.calculate()) diff --git a/final_task/tests/test_check_manager.py b/final_task/tests/test_check_manager.py new file mode 100644 index 00000000..c235aa67 --- /dev/null +++ b/final_task/tests/test_check_manager.py @@ -0,0 +1,122 @@ +import unittest +import operator +import math +from pycalc.check_manager import check_expression, number_check, operator_check, \ + function_check, check_parsing_list +from pycalc.operator_manager import operator_dict, create_func_dict + +function_dict = create_func_dict() + + +class TestCheckManager(unittest.TestCase): + + def test_check_expression_empty(self): + with self.assertRaises(SyntaxError): + check_expression('') + + def test_check_expression_extra_close_bracket(self): + with self.assertRaises(SyntaxError): + check_expression('())') + + def test_check_expression_extra_open_bracket(self): + with self.assertRaises(SyntaxError): + check_expression('(()') + + def test_check_expression_return_expression(self): + expression_line = check_expression('2+3') + self.assertEqual('2+3', expression_line) + + def test_check_parsing_list_start_with_operator_add(self): + parsing_list = check_parsing_list(['+', 42], function_dict) + self.assertEqual(['+', 42], parsing_list) + + def test_check_parsing_list_start_with_operator_sub(self): + parsing_list = check_parsing_list(['-', 42], function_dict) + self.assertEqual(['-', 42], parsing_list) + + def test_check_parsing_list_start_with_operator_not_add_not_sub(self): + with self.assertRaises(SyntaxError): + check_parsing_list(['*', 2], function_dict) + + def test_check_parsing_list_len_is_one_number(self): + parsing_list = check_parsing_list([42], function_dict) + self.assertEqual([42], parsing_list) + + def test_check_parsing_list_len_is_one_str(self): + with self.assertRaises(SyntaxError): + check_parsing_list(['+'], function_dict) + + def test_check_parsing_list_ends_with_operator(self): + with self.assertRaises(SyntaxError): + check_parsing_list([2, '+'], function_dict) + + def test_check_parsing_list_ends_with_function(self): + with self.assertRaises(SyntaxError): + check_parsing_list([2, 'sin'], function_dict) + + def test_check_parsing_list_positive(self): + parsing_list = check_parsing_list([42, 'sin', '(', 2, '+', 3.2, ')'], function_dict) + self.assertEqual([42, 'sin', '(', 2, '+', 3.2, ')'], parsing_list) + + def test_operator_check_opertors_name(self): + operator_str = ['+', '-', '/', '*', '**', '//', '%', '^', '==', '!=', '>', '>=', '<', '<='] + operator_func = [ + operator.add, + operator.sub, + operator.truediv, + operator.mul, + operator.pow, + operator.floordiv, + operator.mod, + operator.pow, + operator.eq, + operator.ne, + operator.gt, + operator.ge, + operator.lt, + operator.le + ] + for i in range(len(operator_str)): + self.assertEqual(operator_str[i], operator_check(operator_str[i])) + self.assertEqual(operator_func[i], operator_dict[operator_str[i]]['operator']) + + def test_operetor_check_negative(self): + with self.assertRaises(SyntaxError): + operator_check('/+') + + def test_numbert_check_int(self): + self.assertTrue(isinstance(number_check('42'), int)) + + def test_number_check_float(self): + self.assertTrue(isinstance(number_check('42.0'), float)) + + def test_function_check_constanta_e(self): + self.assertEqual(math.e, function_check('e', function_dict)) + + def test_function_check_constanta_pi(self): + self.assertEqual(math.pi, function_check('pi', function_dict)) + + def test_function_check_constanta_tau(self): + self.assertEqual(math.pi*2, function_check('tau', function_dict)) + + def test_function_check_function_dict(self): + function_lst = ['abs', 'round'] + constants = ['e', 'pi', 'tau', 'inf', 'nan'] + for key in math.__dict__.keys(): + if key.startswith('_') or key in constants: + continue + function_lst.append(key) + for i in range(len(function_lst)): + self.assertEqual(function_lst[i], function_check(function_lst[i], function_dict)) + + def test_function_chekc_not_function(self): + with self.assertRaises(SyntaxError): + function_check('log100', function_dict) + + def test_fucntion_check_user_constant(self): + function_dict['user_constant'] = {'operator': 42, 'priority': 0} + self.assertEqual(42, function_check('user_constant', function_dict)) + + def test_fucntion_check_user_function(self): + function_dict['user_function'] = {'operator': 'user_function', 'priority': 0} + self.assertEqual('user_function', function_check('user_function', function_dict)) diff --git a/final_task/tests/test_converter.py b/final_task/tests/test_converter.py new file mode 100644 index 00000000..b2ae082a --- /dev/null +++ b/final_task/tests/test_converter.py @@ -0,0 +1,207 @@ +import math +import operator +import unittest +from pycalc.converter import Converter +from pycalc.operator_manager import create_func_dict + +function_dict = create_func_dict() + + +class TestConverter(unittest.TestCase): + + def test_init_class(self): + converter = Converter([42, '+', 3], function_dict) + self.assertEqual([42, '+', 3], converter.parsing_list) + self.assertEqual("", converter.last_item) + self.assertEqual([], converter.converted_list) + + def test_clean_add_sub_operators_minus_into_plus_after_bracket(self): + converter = Converter([42], function_dict) + converter.last_item = '--' + converter.converted_list.append('(') + converter._clean_add_sub_operators() + self.assertEqual("", converter.last_item) + + def test_clean_add_sub_operators_minus_into_plus(self): + converter = Converter([42], function_dict) + converter.last_item = '--' + converter.converted_list.append(42) + converter._clean_add_sub_operators() + self.assertEqual('+', converter.last_item) + + def test_clean_add_sub_operators_minus_into_minus(self): + converter = Converter([42], function_dict) + converter.last_item = '---' + converter.converted_list.append(42) + converter._clean_add_sub_operators() + self.assertEqual('-', converter.last_item) + + def test_append_to_converted_list(self): + converter = Converter([42], function_dict) + converter.last_item = '+' + converter._append_to_converted_list(42, '+', 8) + self.assertEqual([42, '+', 8], converter.converted_list) + self.assertEqual("", converter.last_item) + + def test_number_converter_with_minus_after_zero(self): + converter = Converter([42], function_dict) + converter.last_item = '-' + converter.converted_list.append(0) + test_number = 42 + converter._number_converter(test_number) + self.assertEqual([0, {'operator': operator.sub, 'priority': 2}, 42], converter.converted_list) + + def test_number_converter_with_minus_after_close_bracket(self): + converter = Converter([42], function_dict) + converter.last_item = '-' + converter.converted_list.append(')') + test_number = 42 + converter._number_converter(test_number) + self.assertEqual([')', + {'operator': operator.sub, + 'priority': 4}, + 42], + converter.converted_list) + + def test_number_converter_with_minus_after_operand(self): + converter = Converter([42], function_dict) + converter.last_item = '-' + converter.converted_list.append('(') + test_number = 42 + converter._number_converter(test_number) + self.assertEqual(['(', -42], converter.converted_list) + + def test_number_converter_without_minus(self): + converter = Converter([42], function_dict) + converter.last_item = '+' + test_number = 42 + converter._number_converter(test_number) + self.assertEqual([42], converter.converted_list) + + def test_operator_converter_collect_all_plus_minus(self): + converter = Converter([42], function_dict) + operator_str = '-' + converter._operator_converter(operator_str) + self.assertEqual('-', converter.last_item) + operator_str = '+' + converter._operator_converter(operator_str) + self.assertEqual('-+', converter.last_item) + + def test_operator_converter_add_to_converted_list(self): + converter = Converter([42], function_dict) + operator_str = ['/', '*', '**', '//', '%', '^', '==', '!=', '>', '>=', '<', '<='] + operator_func = [ + operator.truediv, + operator.mul, + operator.pow, + operator.floordiv, + operator.mod, + operator.pow, + operator.eq, + operator.ne, + operator.gt, + operator.ge, + operator.lt, + operator.le + ] + converter.converted_list.append([42]) + for i in range(len(operator_str)): + converter._operator_converter(operator_str[i]) + self.assertEqual(operator_func[i], converter.converted_list[1]['operator']) + converter.converted_list.pop() + + def test_operator_converter_raise_exception_after_operator(self): + converter = Converter([42], function_dict) + converter.converted_list.append({'operator': operator.truediv}) + operator_str = '/' + with self.assertRaises(SyntaxError): + converter._operator_converter(operator_str) + + def test_function_converter_add_negative_function_after_operand(self): + converter = Converter([42], function_dict) + converter.last_item = '-' + converter.converted_list.append(42) + function_str = 'sin' + converter._function_converter(function_str) + self.assertEqual([42, + {'operator': operator.add, 'priority': 4}, + -1, + {'operator': operator.mul, 'priority': 3}, + {'operator': math.sin, 'priority': 0}], + converter.converted_list) + + def test_function_converter_add_negative_function_after_open_bracket(self): + converter = Converter(42, function_dict) + converter.last_item = '-' + converter.converted_list.append('(') + function_str = 'sin' + converter._function_converter(function_str) + self.assertEqual(['(', + -1, + {'operator': operator.mul, 'priority': 3}, + {'operator': math.sin, 'priority': 0}], + converter.converted_list) + + def test_function_converter_add_negative_function_after_close_bracket(self): + converter = Converter([42], function_dict) + converter.last_item = '-' + converter.converted_list.append(')') + function_str = 'sin' + converter._function_converter(function_str) + self.assertEqual([')', + {'operator': operator.add, 'priority': 4}, + -1, + {'operator': operator.mul, 'priority': 3}, + {'operator': math.sin, 'priority': 0}], + converter.converted_list) + + def test_function_converter_add_positive_function(self): + converter = Converter([42], function_dict) + function_str = 'sin' + converter._function_converter(function_str) + self.assertEqual([{'operator': math.sin, 'priority': 0}], converter.converted_list) + + def test_converter_add_zero_into_beginning_with_unary_sub(self): + converter = Converter(['-', 42], function_dict) + converter.converter() + self.assertEqual([0, {'operator': operator.sub, 'priority': 2}, 42], + converter.converted_list) + + def test_converter_remove_space(self): + converter = Converter([42, ' '], function_dict) + converter.converter() + self.assertEqual([42], converter.converted_list) + + def test_converter_append_append_clean_add_operator(self): + converter = Converter([42, '+', '-', '-', 8], function_dict) + converter.converter() + self.assertEqual([42, {'operator': operator.add, 'priority': 4}, 8], + converter.converted_list) + + def test_converter_append_int(self): + converter = Converter([42], function_dict) + converter.converter() + self.assertEqual([42], converter.converted_list) + + def test_converter_append_float(self): + converter = Converter([42.8], function_dict) + converter.converter() + self.assertEqual([42.8], converter.converted_list) + + def test_converter_append_operator(self): + converter = Converter([42, '*', 8], function_dict) + converter.converter() + self.assertEqual([42, {'operator': operator.mul, 'priority': 3}, 8], + converter.converted_list) + + def test_converter_append_function(self): + converter = Converter(['sin', '(', 42, ')'], function_dict) + converter.converter() + self.assertEqual([{'operator': math.sin, 'priority': 0}, '(', 42, ')'], + converter.converted_list) + + def test_converter_append_bracket_with_minus(self): + converter = Converter(['(', '-', 42, ')'], function_dict) + converter.converter() + self.assertEqual(['(', -42, ')'], + converter.converted_list) diff --git a/final_task/tests/test_operator_manager.py b/final_task/tests/test_operator_manager.py new file mode 100644 index 00000000..c7741c6e --- /dev/null +++ b/final_task/tests/test_operator_manager.py @@ -0,0 +1,12 @@ +import unittest +import math +from pycalc.operator_manager import create_func_dict + + +class TestOperatorManager(unittest.TestCase): + + def test_create_func_dict(self): + test_func_name = dir(math) + ['round', 'abs'] + check_dict = create_func_dict() + for key in check_dict.keys(): + self.assertTrue(key in test_func_name) diff --git a/final_task/tests/test_pycalc.py b/final_task/tests/test_pycalc.py new file mode 100644 index 00000000..bab61146 --- /dev/null +++ b/final_task/tests/test_pycalc.py @@ -0,0 +1,43 @@ +import unittest +from pycalc.pycalc import main +from pycalc.calculator import Calculator +from pycalc.operator_manager import create_func_dict + +function_dict = create_func_dict() + + +class TestPycalc(unittest.TestCase): + + def test_calc(self): + test_expression = 'round(-2+(tau^2-sin(pi/2)*2+6.5))' + calc = Calculator(test_expression, function_dict) + self.assertEqual(42, calc.calculate()) + + def test_syntax_error_in_init_class(self): + test_expression = '15*' + with self.assertRaises(SyntaxError): + Calculator(test_expression, function_dict) + + def test_syntax_error_in_class_method(self): + test_expression = 'sin(pi, 45)' + calc = Calculator(test_expression, function_dict) + with self.assertRaises(SyntaxError): + calc.calculate() + + def test_zero_division_error(self): + test_expression = '42/0' + calc = Calculator(test_expression, function_dict) + with self.assertRaises(ZeroDivisionError): + calc.calculate() + + def test_math_error(self): + test_expression = 'sqrt(-10)' + calc = Calculator(test_expression, function_dict) + with self.assertRaises(ValueError): + calc.calculate() + + def test_overflow_error(self): + test_expression = 'exp(1000.0)' + calc = Calculator(test_expression, function_dict) + with self.assertRaises(OverflowError): + calc.calculate() diff --git a/final_task/tests/test_split_operators.py b/final_task/tests/test_split_operators.py new file mode 100644 index 00000000..f1ac9307 --- /dev/null +++ b/final_task/tests/test_split_operators.py @@ -0,0 +1,277 @@ +import unittest +import math +from pycalc.split_operators import SplitOperators +from pycalc.operator_manager import create_func_dict + + +function_dict = create_func_dict() + + +class TestSplitOperators(unittest.TestCase): + + def test_init_class_method(self): + expression = SplitOperators('1', {'tan': {'operator': math.tan, 'priority': 0}}) + self.assertEqual('1', expression.expression_line) + self.assertEqual({'tan': {'operator': math.tan, 'priority': 0}}, expression.function_dict) + self.assertEqual([], expression.parsing_list) + self.assertEqual("", expression.last_number) + self.assertEqual("", expression.last_letter) + self.assertEqual("", expression.last_symbol) + self.assertEqual(False, expression.blank_item) + self.assertEqual("", expression.brackets) + self.assertEqual(False, expression.arguments_needs) + self.assertEqual("", expression.function_arguments) + self.assertEqual("", expression.brackets_in_arguments) + + def test_symbol_append_to_parsing_list(self): + expression = SplitOperators('', function_dict) + expression.last_symbol = '*' + expression._append_to_parsing_list() + self.assertEqual(['*'], expression.parsing_list) + self.assertEqual('', expression.last_symbol) + + def test_number_append_to_parsing_list(self): + expression = SplitOperators('42', function_dict) + expression.last_number = '42' + expression._append_to_parsing_list() + self.assertEqual([42], expression.parsing_list) + self.assertEqual('', expression.last_number) + + def test_letter_append_to_parsing_list(self): + expression = SplitOperators('42', function_dict) + expression.last_letter = 'sin' + expression._append_to_parsing_list() + self.assertEqual(['sin'], expression.parsing_list) + self.assertEqual('', expression.last_letter) + self.assertEqual(True, expression.arguments_needs) + + def test_number_parser_with_symbol(self): + expression = SplitOperators('42', function_dict) + expression.last_symbol = '+' + expression._number_parser('2') + self.assertEqual(['+'], expression.parsing_list) + self.assertEqual('2', expression.last_number) + + def test_number_parser_with_letter(self): + expression = SplitOperators('42', function_dict) + expression.last_letter = 'log' + expression._number_parser('10') + self.assertEqual('log10', expression.last_letter) + self.assertEqual('', expression.last_number) + + def test_number_parser_with_first_number(self): + expression = SplitOperators('42', function_dict) + expression._number_parser('1') + self.assertEqual('1', expression.last_number) + self.assertEqual([], expression.parsing_list) + + def test_number_parser_with_second_number(self): + expression = SplitOperators('42', function_dict) + expression.last_number = '4' + expression._number_parser('2') + self.assertEqual('42', expression.last_number) + self.assertEqual([], expression.parsing_list) + + def test_number_parser_two_comma(self): + expression = SplitOperators('42', function_dict) + expression.last_number = '1.0' + with self.assertRaises(SyntaxError): + expression._number_parser('.') + + def test_number_parser_space_into_operand(self): + expression = SplitOperators('42', function_dict) + expression.blank_item = True + expression.parsing_list.append(1) + with self.assertRaises(SyntaxError): + expression._number_parser(2) + + def test_number_parser_blank_item_from_true_to_false(self): + expression = SplitOperators('42', function_dict) + expression.blank_item = True + expression.parsing_list.append('+') + expression._number_parser('2') + self.assertEqual(False, expression.blank_item) + self.assertEqual(['+'], expression.parsing_list) + + def test_function_parser_with_symbol(self): + expression = SplitOperators('42', function_dict) + expression.last_symbol = '+' + expression._function_parser('s') + self.assertEqual(['+'], expression.parsing_list) + self.assertEqual('s', expression.last_letter) + + def test_function_parser_second_letter(self): + expression = SplitOperators('42', function_dict) + expression.last_letter = 's' + expression._function_parser('i') + self.assertEqual([], expression.parsing_list) + self.assertEqual('si', expression.last_letter) + + def test_twice_operator_parser_blank_item_from_true_to_false(self): + expression = SplitOperators('42', function_dict) + expression.blank_item = True + expression.parsing_list.append(1) + expression._twice_operator_parser('*') + self.assertEqual(False, expression.blank_item) + self.assertEqual('*', expression.last_symbol) + self.assertEqual([1], expression.parsing_list) + + def test_twice_operator_add_second_symbol(self): + expression = SplitOperators('42', function_dict) + expression.last_symbol = '*' + expression._twice_operator_parser('*') + self.assertEqual('**', expression.last_symbol) + self.assertEqual([], expression.parsing_list) + + def test_twice_operator_parser_wrong_duble_operator(self): + expression = SplitOperators('42', function_dict) + expression.last_symbol = '**' + with self.assertRaises(SyntaxError): + expression._twice_operator_parser('*') + + def test_twice_operator_parser_space_into_operator(self): + expression = SplitOperators('42', function_dict) + expression.blank_item = True + expression.parsing_list.append('*') + with self.assertRaises(SyntaxError): + expression._twice_operator_parser('*') + + def test_simple_operator(self): + expression = SplitOperators('42', function_dict) + expression._simple_operator_bracket_parser('/') + self.assertEqual(['/'], expression.parsing_list) + + def test_simple_operator_add_open_bracket(self): + expression = SplitOperators('42', function_dict) + expression._simple_operator_bracket_parser('(') + self.assertEqual(['('], expression.parsing_list) + + def test_simple_operator_add_closed_bracket(self): + expression = SplitOperators('42', function_dict) + expression.brackets = '(' + expression._simple_operator_bracket_parser(')') + self.assertEqual([')'], expression.parsing_list) + + def test_simple_operator_add_wrong_bracket(self): + expression = SplitOperators('42', function_dict) + with self.assertRaises(SyntaxError): + expression._simple_operator_bracket_parser(')') + + def test_simple_operator_blank_symbol(self): + expression = SplitOperators('42', function_dict) + expression._simple_operator_bracket_parser(' ') + self.assertEqual([], expression.parsing_list) + + def test_simple_operator_bracket_for_function_argument(self): + expression = SplitOperators('42', function_dict) + expression.arguments_needs = True + expression._simple_operator_bracket_parser('(') + self.assertEqual('(', expression.brackets_in_arguments) + self.assertEqual(True, expression.arguments_needs) + + def test_split_operators_number_and_blank_i(self): + expression = SplitOperators('42 +', function_dict) + expression.split_operators() + self.assertEqual(True, expression.blank_item) + self.assertEqual([42, '+'], expression.parsing_list) + self.assertEqual('', expression.last_number) + self.assertEqual('', expression.last_letter) + self.assertEqual('', expression.last_symbol) + + def test_split_operators_number(self): + expression = SplitOperators('42', function_dict) + expression.split_operators() + self.assertEqual('', expression.last_number) + self.assertEqual([42], expression.parsing_list) + self.assertEqual('', expression.last_letter) + self.assertEqual('', expression.last_symbol) + + def test_split_operators_function(self): + expression = SplitOperators('sin', function_dict) + expression.split_operators() + self.assertEqual(['sin'], expression.parsing_list) + self.assertEqual('', expression.last_number) + self.assertEqual('', expression.last_letter) + self.assertEqual('', expression.last_symbol) + + def test_split_operators_twice_operator_one_symbol(self): + expression = SplitOperators('42/7', function_dict) + expression.split_operators() + self.assertEqual([42, '/', 7], expression.parsing_list) + self.assertEqual('', expression.last_number) + self.assertEqual('', expression.last_letter) + self.assertEqual('', expression.last_symbol) + + def test_split_operators_twice_operator_two_symbol(self): + expression = SplitOperators('42//7', function_dict) + expression.split_operators() + self.assertEqual([42, '//', 7], expression.parsing_list) + self.assertEqual('', expression.last_number) + self.assertEqual('', expression.last_letter) + self.assertEqual('', expression.last_symbol) + + def test_split_operators_brackets(self): + expression = SplitOperators('(42)', function_dict) + expression.split_operators() + self.assertEqual(['(', 42, ')'], expression.parsing_list) + self.assertEqual('', expression.last_number) + self.assertEqual('', expression.last_letter) + self.assertEqual('', expression.last_symbol) + + def test_split_operators_extra_operator(self): + expression = SplitOperators('42//', function_dict) + with self.assertRaises(SyntaxError): + expression.split_operators() + + def test_split_operators_with_function_arguments(self): + expression = SplitOperators('log(2, 2*e)', function_dict) + expression.split_operators() + self.assertEqual(['log', ('2', '2*e')], expression.parsing_list) + self.assertEqual('', expression.last_number) + self.assertEqual('', expression.last_letter) + self.assertEqual('', expression.last_symbol) + + def test_collect_function_arguments_first_add_to_arguments(self): + expression = SplitOperators('42', function_dict) + expression.arguments_needs = True + expression._collect_function_arguments('42') + self.assertEqual('42', expression.function_arguments) + self.assertEqual(True, expression.arguments_needs) + + def test_collect_function_arguments_add_bracket_to_arguments(self): + expression = SplitOperators('42', function_dict) + expression.arguments_needs = True + expression._collect_function_arguments('(') + self.assertEqual('(', expression.function_arguments) + self.assertEqual(True, expression.arguments_needs) + self.assertEqual('(', expression.brackets_in_arguments) + + def test_collect_function_arguments_add_last_bracket_to_arguments_with_split(self): + expression = SplitOperators('42', function_dict) + expression.brackets_in_arguments = '(' + expression.function_arguments = '42, 8' + expression._collect_function_arguments(')') + self.assertEqual(('42', ' 8'), expression.parsing_list[-1]) + self.assertEqual((''), expression.function_arguments) + self.assertEqual(False, expression.arguments_needs) + self.assertEqual('', expression.brackets_in_arguments) + + def test_collect_function_arguments_add_last_bracket_to_arguments_without_split(self): + expression = SplitOperators('42', function_dict) + expression.brackets_in_arguments = '(' + expression.function_arguments = '42' + expression._collect_function_arguments(')') + self.assertEqual('', expression.function_arguments) + self.assertEqual(('42',), expression.parsing_list[-1]) + self.assertEqual(False, expression.arguments_needs) + self.assertEqual('', expression.brackets_in_arguments) + + def test_collect_function_arguments_add_arguments_as_functions(self): + expression = SplitOperators('42', function_dict) + expression.brackets_in_arguments = '(' + expression.function_arguments = 'log(42,8)*pi' + expression._collect_function_arguments(')') + self.assertEqual(('log(42,8)*pi',), expression.parsing_list[-1]) + self.assertEqual('', expression.function_arguments) + self.assertEqual(False, expression.arguments_needs) + self.assertEqual('', expression.brackets_in_arguments) diff --git a/final_task/tests/test_stack_manager.py b/final_task/tests/test_stack_manager.py new file mode 100644 index 00000000..259d9a52 --- /dev/null +++ b/final_task/tests/test_stack_manager.py @@ -0,0 +1,33 @@ +import unittest +from pycalc.stack_manager import Stack + + +class TestStack(unittest.TestCase): + + def setUp(self): + self.stack = Stack() + + def test_init_class(self): + self.assertEqual([], self.stack.stack) + + def test_put_on_stack(self): + self.stack.put_on_stack(42) + self.assertEqual([42], self.stack.stack) + + def test_top(self): + self.stack.put_on_stack(42) + self.assertEqual(42, self.stack.top()) + self.assertEqual([42], self.stack.stack) + + def test_take_from_stack(self): + self.stack.put_on_stack(42) + result = self.stack.take_from_stack() + self.assertEqual(42, result) + self.assertEqual([], self.stack.stack) + + def test_is_empty_true(self): + self.assertTrue(self.stack.is_empty()) + + def test_is_empty_false(self): + self.stack.put_on_stack(42) + self.assertFalse(self.stack.is_empty()) diff --git a/pycalc_checker.py b/pycalc_checker.py index aa67c85b..bf2cb9ad 100644 --- a/pycalc_checker.py +++ b/pycalc_checker.py @@ -23,6 +23,7 @@ "10^(2+1)": 10**(2+1), "100/3^2": 100/3**2, "100/3%2^2": 100/3%2**2, + "-15//2": -15//2, } @@ -36,6 +37,7 @@ "pow(2, 3)": pow(2, 3), "abs(-5)": abs(-5), "round(123.4567890)": round(123.4567890), + "fsum([1,2,3,4])": fsum([1,2,3,4]), } ASSOCIATIVE = { @@ -88,6 +90,10 @@ "(((((", "abs", "pow(2, 3, 4)", + "2+/3", + "/2", + "2***2", + "sin)pi(", ]