-
Notifications
You must be signed in to change notification settings - Fork 27
Mikhail Sauchuk #15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Mikhail Sauchuk #15
Changes from all commits
852e30c
ace43c3
62abf56
47c3166
c110200
bec2b2a
2025e62
4c79af9
4737325
f789ce8
164b049
9aa5958
5302999
765b59a
4587c42
4fe0ddc
ab26ff7
c692c36
9f2bef9
5b20da7
8cf47b2
e35b482
953073e
94124bb
cc24c2d
951cfe3
486cab9
c178130
bafd158
031417b
367eb44
ea57e21
c1778ed
07e2add
a1f94cd
b61dba0
6fe197a
fdb1b63
bb7f75d
3142b89
9685348
4f3a030
8ac1b50
a245b70
332f78a
0e6db61
c415e31
d4d85e0
011ba7a
75ac4b8
6f4df28
e5b7154
dadeec9
bb1eee8
4900fae
fd104b0
fd404ab
006f403
3641d09
d9ad6a5
efc3b05
bfb6f74
0147aa3
107d675
d9e9781
7c225c9
1c1b875
bf531a9
b9b3e65
5969be8
9fcc8c4
352d09c
7730f18
beb7025
e976ce0
3ad8c06
e3d0d6d
ed3f7e9
6a123b5
c44cb80
a4d0cfd
8df0444
374662c
5662e5b
361f325
ff1771c
b5f09dc
0e661c6
2bff82d
d531321
a72cf15
8a1b434
e55dfc4
331c0f2
20a8990
622f711
1b47d24
e1e9c62
f29720e
da41b6e
3e4644e
9356d67
03117d0
955f642
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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(): | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. вызов метода if parsing_list[-1] in operator_dict:
... |
||
| 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): | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Мне кажется, что это лишняя функция и можно было легко обойтись без нее, просто проверять наличие ключа в словаре вместо вызова функции |
||
| """ | ||
| 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(): | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. и все же если оставлять эту функцию именно в таком виде, то я бы лучше ее написал вот так if operator_symbol not in operator_dict:
raise SyntaxError('Typo in math operator!')
return operator_symbol |
||
| 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. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| 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) | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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): | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Название функции должно показывать действие. По смыслу больше подходит |
||
| """ | ||
| 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 | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Этот докстринг в точности повторяет код строчка за строчкой :)
( +
__init__не генерирует объект, а инициализирует его)