From 1d1721ae72d7ce685104edea9cf898da11c4fd60 Mon Sep 17 00:00:00 2001 From: Pavel Kuzmich Date: Wed, 19 Dec 2018 04:15:33 +0300 Subject: [PATCH 01/23] add setup file --- final_task/setup.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/final_task/setup.py b/final_task/setup.py index e69de29..2b30566 100644 --- a/final_task/setup.py +++ b/final_task/setup.py @@ -0,0 +1,11 @@ +from setuptools import setup, find_packages + +setup( + name='pycalc', + version='1.0', + author='Pavel Kuzmich', + author_email='pavelkuz99@outlook.com', + description='Pure Python command-line calculator', + packages=find_packages(), + scripts=['calculator/pycalc'] +) From 8edcd65b30fd9cfaf08bca87a76fa7541821e5f2 Mon Sep 17 00:00:00 2001 From: Pavel Kuzmich Date: Wed, 19 Dec 2018 04:21:59 +0300 Subject: [PATCH 02/23] modifed setup file --- final_task/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/final_task/setup.py b/final_task/setup.py index 2b30566..d69f7f7 100644 --- a/final_task/setup.py +++ b/final_task/setup.py @@ -7,5 +7,5 @@ author_email='pavelkuz99@outlook.com', description='Pure Python command-line calculator', packages=find_packages(), - scripts=['calculator/pycalc'] + scripts=['calculator/pycalc.py'] ) From 807cfe8a6adb4ef6a7b99024e8f61e42708d34ed Mon Sep 17 00:00:00 2001 From: Pavel Kuzmich Date: Wed, 19 Dec 2018 04:22:50 +0300 Subject: [PATCH 03/23] implementation without test --- final_task/calculator/pycalc.py | 478 ++++++++++++++++++++++++++++++++ 1 file changed, 478 insertions(+) create mode 100755 final_task/calculator/pycalc.py diff --git a/final_task/calculator/pycalc.py b/final_task/calculator/pycalc.py new file mode 100755 index 0000000..cedc62f --- /dev/null +++ b/final_task/calculator/pycalc.py @@ -0,0 +1,478 @@ +from re import compile +from io import StringIO +from tokenize import generate_tokens +import math +from sys import exit +from argparse import ArgumentParser +from numbers import Number +from collections import namedtuple + + +class UnbalancedParenthesesError(Exception): + def __init__(self, message): + super(UnbalancedParenthesesError, self).__init__(message) + + +class UnknownFunctionError(Exception): + def __init__(self, message): + super(UnknownFunctionError, self).__init__(message) + + +class MissingParameterError(Exception): + def __init__(self, message): + super(MissingParameterError, self).__init__(message) + + +class UnknownSymbolError(Exception): + def __init__(self, message): + super(UnknownSymbolError, self).__init__(message) + + +class UnexpectedSpaceError(Exception): + def __init__(self, message): + super(UnexpectedSpaceError, self).__init__(message) + + +class RPN: + """ + Reverse Polish notation converter and handler class + """ + + def __init__(self): + self.stack, self.output, self.expression, self.tokens = [], [], '', [] + self.postfix_ops = {'!': self.factorial} + self.prefix_ops = {a: getattr(math, a) for a in dir(math) if callable(getattr(math, a))} + other_prefix_op = { + 'log': self.logarithm, + 'log2': self.logarithm_two, + 'log10': self.logarithm_ten, + 'pow': self.power, + 'sqrt': self.square_root, + 'ln': self.logarithm_e, + 'abs': abs, + 'round': round, + 'minus': self.unary_minus, + 'plus': self.unary_plus, + } + self.prefix_ops.update(other_prefix_op) + sign = namedtuple('sign', 'priority action') + self.signs = { + '^': sign(4, self.power), + '**': sign(4, self.power), + '/': sign(3, self.divide), + '//': sign(3, self.int_divide), + '%': sign(3, self.division_rest), + '*': sign(3, lambda digit1, digit2: digit1 * digit2), + '+': sign(2, lambda digit1, digit2: digit1 + digit2), + '-': sign(2, lambda digit1, digit2: digit1 - digit2), + '(': sign(1, None), + ')': sign(1, None), + '<': sign(0, lambda digit1, digit2: digit1 < digit2), + '<=': sign(0, lambda digit1, digit2: digit1 <= digit2), + '=': sign(0, lambda digit1, digit2: digit1 == digit2), + '==': sign(0, lambda digit1, digit2: digit1 == digit2), + '!=': sign(0, lambda digit1, digit2: digit1 != digit2), + '>=': sign(0, lambda digit1, digit2: digit1 >= digit2), + '>': sign(0, lambda digit1, digit2: digit1 > digit2) + } + values = {attr: getattr(math, attr) for attr in dir(math) if isinstance(getattr(math, attr), Number)} + self.const_values = values + self.all_ops = {**self.postfix_ops, **self.prefix_ops, **self.signs} + + def clear_stack(self): + """ + Clears class attribute 'stack' + """ + self.stack = [] + + @staticmethod + def is_num(item): + """ + Checks whether this token is number + """ + try: + float(item) + return True + except (ValueError, TypeError): + pass + return False + + @staticmethod + def unary_minus(digit): + """ + Changes 'digit' to '-digit' + """ + return (-1) * digit + + @staticmethod + def unary_plus(digit): + """ + Changes 'digit' to '+digit' + """ + return digit + + @staticmethod + def factorial(digit): + """ + Calculates factorial on a digit + """ + if digit < 0: + raise ValueError('can\'t count factorial of negative number') + elif not str(digit).isdigit(): + raise ValueError('can\'t count factorial of fractional number') + else: + return math.factorial(digit) + + @staticmethod + def logarithm(digit1, digit2): + """ + Calculates logarithm of digit1 by base digit2 + """ + if digit2 == 1: + raise ZeroDivisionError('cant\'t count logarithm by base 1') + elif digit1 <= 0: + raise ValueError('can\'t count non-positive logarithm') + else: + return math.log(digit1, digit2) + + @staticmethod + def logarithm_e(digit): + """ + Calculates natural logarithm of digit by base e + """ + if digit > 0: + return math.log(digit) + else: + raise ValueError('can\'t count non-positive logarithm') + + def resolve_log(self): + """ + If log() takes two parameter leaves it the same + If log() takes one parameter changes log() to ln() + """ + open_parentheses_count = 0 + close_parentheses_count = 0 + for index, token in enumerate(self.tokens): + if token == 'log': + n_args = 1 + for new_token in self.tokens[index + 1:]: + if new_token == '(': + open_parentheses_count += 1 + if new_token == ',': + n_args = 2 + if new_token == ')': + close_parentheses_count += 1 + if open_parentheses_count == close_parentheses_count: + break + if n_args == 1: + self.tokens[index] = 'ln' + + @staticmethod + def logarithm_two(digit): + """ + Calculates logarithm by base two + """ + if digit > 0: + return math.log2(digit) + else: + raise ValueError('can\'t count non-positive logarithm by base 2') + + @staticmethod + def logarithm_ten(digit): + """ + Calculates logarithm by base ten + """ + if digit > 0: + return math.log10(digit) + else: + raise ValueError('can\'t count non-positive logarithm by base 10') + + @staticmethod + def power(digit1, digit2): + """ + Calculates digit2-power of digit1 + """ + if digit1 < 0 and not digit2.is_integer(): + raise ValueError('can\'t raise negative number to fractional power') + else: + return pow(digit1, digit2) + + @staticmethod + def square_root(digit): + """ + Calculates square root of digit + """ + if digit >= 0: + return math.sqrt(digit) + else: + raise ValueError('can\'t count square root of negative number') + + @staticmethod + def divide(digit1, digit2): + """ + Calculates digit1 divided by digit2 + """ + if digit2 == 0: + raise ZeroDivisionError('can\'t divide by zero') + else: + return digit1 / digit2 + + @staticmethod + def int_divide(digit1, digit2): + """ + Performes integer division of digit1 by digit2 + """ + if digit2 == 0: + raise ZeroDivisionError('can\'t divide by zero') + else: + return digit1 // digit2 + + @staticmethod + def division_rest(digit1, digit2): + """ + Calculates the rest of division of ditit1 by digit2 + """ + if digit2 == 0: + raise ZeroDivisionError('can\'t divide by zero') + else: + return digit1 % digit2 + + def add_implicit_multiply(self, tokens_list): + """ + Adds implicit multiplication to the new tokens list + Returns resolved list + """ + resolved_list = [] + for index, token in enumerate(tokens_list): + prev = tokens_list[index - 1] + if index == 0: + resolved_list.append(token) + elif self.is_num(token) and prev == ')': + resolved_list.append('*') + resolved_list.append(token) + elif token == '(' and prev == ')': + resolved_list.append('*') + resolved_list.append(token) + elif token == '(' and self.is_num(prev): + resolved_list.append('*') + resolved_list.append(token) + else: + resolved_list.append(token) + return resolved_list + + def resolve_unary(self, tokens_list): + """ + Resolves all unary '-' and '+' operations changing them to 'minus' and 'plus' functions + Returns resolved list + """ + resolved_list = [] + for index, token in enumerate(tokens_list): + prev = tokens_list[index - 1] + if token == '-' or token == '+': + if index == 0: + resolved_list.append('minus') if token == '-' else resolved_list.append('plus') + elif prev == ')' or prev in self.const_values or self.is_num(prev): + resolved_list.append(token) + else: + resolved_list.append('minus') if token == '-' else resolved_list.append('plus') + else: + resolved_list.append(token) + return resolved_list + + @staticmethod + def create_tokens_list(some_string): + """ + Creates tokens list from math expressions string + """ + line = generate_tokens(StringIO(some_string).readline) + return [token[1] for token in line if token[1]] + + def convert_to_rpn(self, text): + """ + Converts initial math expression to Reverse Polish Notation while solving log(), unary operation + and adds implicit multiplication + Return tokens list in Reverse Polish Notation + """ + self.tokens = self.create_tokens_list(text) + self.resolve_log() + self.tokens = self.resolve_unary(self.tokens) + self.tokens = self.add_implicit_multiply(self.tokens) + for item in self.tokens: + if self.is_num(item) or item in self.postfix_ops or item in self.const_values: + self.output.append(item) + elif item == '(' or item in self.prefix_ops: + self.stack.append(item) + elif item == ')': + for element in reversed(self.stack): + if element != '(': + self.output.append(self.stack.pop()) + else: + self.stack.pop() + break + elif item in self.signs: + for element in reversed(self.stack): + if self.stack[-1] in self.prefix_ops \ + or self.signs[self.stack[-1]].priority > self.signs[item].priority \ + or self.signs[self.stack[-1]].priority == self.signs[item].priority and item != '^': + self.output.append(self.stack.pop()) + self.stack.append(item) + elif item == ',': + pass + else: + raise UnknownFunctionError(f'wrong operation "{item}"') + for element in reversed(self.stack): + self.output.append(self.stack.pop()) + self.clear_stack() + return list(self.output) + + def pop_one(self): + """ + Pops one item from stack + """ + return float(self.stack.pop()) + + def pop_two(self): + """ + Pops two items from stack + """ + y = float(self.stack.pop()) + x = float(self.stack.pop()) + return x, y + + def handle_operations(self, rpn_expression): + """ + Handles all operations in RPN tokens list + Return result of calculation + """ + for op in rpn_expression: + # print('Stack: ', self.stack) + if op in self.const_values: + self.stack.append(self.const_values[op]) + elif op not in self.all_ops: + self.stack.append(op) + elif op in self.signs: + try: + function = self.signs[op].action + x, y = self.pop_two() + self.stack.append(function(x, y)) + except IndexError: + raise MissingParameterError(f'not enough operands for "{op}" operation') + elif op in self.prefix_ops or self.postfix_ops: + try: + if op in ('fmod', 'gcd', 'isclose', 'ldexp', 'remainder', 'log', 'pow', 'atan2'): + function = self.all_ops[op] + x, y = self.pop_two() + self.stack.append(function(x, y)) + else: + function = self.all_ops[op] + self.stack.append(function(self.pop_one())) + except IndexError: + raise MissingParameterError(f'not enough operands for "{op}" operation') + + return self.stack[0] + + @staticmethod + def parse_expression(): + """ + Creates command-line arguments parser + Returns parsed argument + """ + parser = ArgumentParser(description='Pure Python command-line calculator') + parser.add_argument('EXPRESSION', help='expression string to evaluate', action='store_true') + parsed, args = parser.parse_known_args() + return args[0] + + +class Check(RPN): + def __init__(self): + super(Check, self).__init__() + + def check_for_numbers(self, text): + """ + Checks whether expression has no operands + :param text: initial math expression + """ + tokens = self.create_tokens_list(text) + numbers_count = 0 + for token in tokens: + if self.is_num(token) or token in self.const_values: + numbers_count += 1 + if numbers_count == 0: + raise MissingParameterError('no numbers or constants in expression') + + @staticmethod + def check_parentheses(text): + """ + Checks whether parentheses are balanced + """ + n = abs(text.count('(') - text.count(')')) + if text.count('(') > text.count(')'): + raise UnbalancedParenthesesError(f'expression has {n} unclosed parentheses') + elif text.count('(') < text.count(')'): + raise UnbalancedParenthesesError(f'expression has {n} redundant closing parentheses') + if n == 0: + pass + + @staticmethod + def check_for_symbols(some_string): + """ + Checks whether unsupported symbols are in the string + """ + regex = compile('[;@_#$&?|}{~":]') + if regex.search(some_string): + raise UnknownSymbolError(f'unknown symbols "{regex.search(some_string).group()}"') + else: + pass + + @staticmethod + def check_spaces(some_string): + """ + Checks whether unexpected spaces are in the string + """ + for index, char in enumerate(some_string): + try: + nxt = some_string[index + 1] + prev = some_string[index - 1] + if char == ' ': + if nxt.isdigit() and prev.isdigit(): + raise UnexpectedSpaceError('unexpected space between numbers') + elif nxt == ' ' or prev == ' ': + raise UnexpectedSpaceError('unexpected double space') + elif nxt == '.' and prev.isdigit() or nxt.isdigit() and prev == '.': + raise UnexpectedSpaceError('unexpected space between/or in fractional numbers') + elif nxt in '<>=!' and prev in '<>=!': + raise UnexpectedSpaceError(f'unexpected space in comparison operation {prev + nxt}') + elif nxt in '*/^' and prev in '*/^': + raise UnexpectedSpaceError(f'unexpected space in operation {prev + nxt}') + elif prev == '(' and nxt == ')': + raise UnexpectedSpaceError('unexpected empty parentheses') + elif prev == ')' and nxt == '.': + raise UnexpectedSpaceError('unexpected fractional number after ")"') + except IndexError: + pass + + def initial_check(self, expression): + """ + Performes initial checks of the expression for errors + """ + self.check_parentheses(expression) + self.check_for_symbols(expression) + self.check_for_numbers(expression) + self.check_spaces(expression) + + +def main(): + try: + rpn = RPN() + check = Check() + rpn.expression = rpn.parse_expression() + check.initial_check(rpn.expression) + rpn_expression = rpn.convert_to_rpn(rpn.expression) + print(rpn.handle_operations(rpn_expression)) + return rpn.handle_operations(rpn_expression) + except Exception as e: + exit(f'ERROR: {e}') + + +if __name__ == "__main__": + main() From 3864c16aef377e2e45d697ffccdaa0efeb6c406f Mon Sep 17 00:00:00 2001 From: Pavel Kuzmich <45352360+pavelkuz99@users.noreply.github.com> Date: Wed, 19 Dec 2018 04:36:15 +0300 Subject: [PATCH 04/23] Update pycalc.py changed some docstrings --- final_task/calculator/pycalc.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/final_task/calculator/pycalc.py b/final_task/calculator/pycalc.py index cedc62f..57baeb5 100755 --- a/final_task/calculator/pycalc.py +++ b/final_task/calculator/pycalc.py @@ -148,7 +148,7 @@ def logarithm_e(digit): def resolve_log(self): """ If log() takes two parameter leaves it the same - If log() takes one parameter changes log() to ln() + If log() takes one parameter changes log() to ln() in place """ open_parentheses_count = 0 close_parentheses_count = 0 @@ -170,7 +170,7 @@ def resolve_log(self): @staticmethod def logarithm_two(digit): """ - Calculates logarithm by base two + Calculates logarithm of digit by base two """ if digit > 0: return math.log2(digit) @@ -180,7 +180,7 @@ def logarithm_two(digit): @staticmethod def logarithm_ten(digit): """ - Calculates logarithm by base ten + Calculates logarithm of digit by base ten """ if digit > 0: return math.log10(digit) From 51cbbedf0b1ee30b2fc37e8c101ea76fb64fe2c9 Mon Sep 17 00:00:00 2001 From: Pavel Kuzmich <45352360+pavelkuz99@users.noreply.github.com> Date: Wed, 19 Dec 2018 04:44:27 +0300 Subject: [PATCH 05/23] Update setup.py tried to fix setup --- final_task/setup.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/final_task/setup.py b/final_task/setup.py index d69f7f7..794f0b7 100644 --- a/final_task/setup.py +++ b/final_task/setup.py @@ -7,5 +7,9 @@ author_email='pavelkuz99@outlook.com', description='Pure Python command-line calculator', packages=find_packages(), - scripts=['calculator/pycalc.py'] + entry_points={ + 'console_scripts': [ + 'pycalc = calculator.pycalc:main', + ] + } ) From ee8f9ae21b855bb5c48c5cdb90224d354b5c80eb Mon Sep 17 00:00:00 2001 From: Pavel Kuzmich <45352360+pavelkuz99@users.noreply.github.com> Date: Wed, 19 Dec 2018 04:57:29 +0300 Subject: [PATCH 06/23] Update setup.py fixed setup file --- final_task/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/final_task/setup.py b/final_task/setup.py index 794f0b7..639ef80 100644 --- a/final_task/setup.py +++ b/final_task/setup.py @@ -9,7 +9,7 @@ packages=find_packages(), entry_points={ 'console_scripts': [ - 'pycalc = calculator.pycalc:main', + 'pycalc=calculator.pycalc:main', ] } ) From 61f26a94ef6db8a3f06443a8ea03684d0bbeef35 Mon Sep 17 00:00:00 2001 From: Pavel Kuzmich <45352360+pavelkuz99@users.noreply.github.com> Date: Wed, 19 Dec 2018 04:57:57 +0300 Subject: [PATCH 07/23] Create __init__.py add __init__.py file --- final_task/calculator/__init__.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 final_task/calculator/__init__.py diff --git a/final_task/calculator/__init__.py b/final_task/calculator/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/final_task/calculator/__init__.py @@ -0,0 +1 @@ + From 3677a8d5f28acbd59e72b81c2eadf90c1f49820c Mon Sep 17 00:00:00 2001 From: Pavel Kuzmich <45352360+pavelkuz99@users.noreply.github.com> Date: Wed, 19 Dec 2018 04:58:55 +0300 Subject: [PATCH 08/23] Update pycalc.py removed redundant print --- final_task/calculator/pycalc.py | 1 - 1 file changed, 1 deletion(-) diff --git a/final_task/calculator/pycalc.py b/final_task/calculator/pycalc.py index 57baeb5..7cf3759 100755 --- a/final_task/calculator/pycalc.py +++ b/final_task/calculator/pycalc.py @@ -468,7 +468,6 @@ def main(): rpn.expression = rpn.parse_expression() check.initial_check(rpn.expression) rpn_expression = rpn.convert_to_rpn(rpn.expression) - print(rpn.handle_operations(rpn_expression)) return rpn.handle_operations(rpn_expression) except Exception as e: exit(f'ERROR: {e}') From a6aa25ddb94026e0682980162e6ab66fb8f91af3 Mon Sep 17 00:00:00 2001 From: Pavel Kuzmich <45352360+pavelkuz99@users.noreply.github.com> Date: Wed, 19 Dec 2018 05:08:55 +0300 Subject: [PATCH 09/23] Update setup.py --- final_task/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/final_task/setup.py b/final_task/setup.py index 639ef80..5447a9a 100644 --- a/final_task/setup.py +++ b/final_task/setup.py @@ -2,7 +2,7 @@ setup( name='pycalc', - version='1.0', + version='1.0.1', author='Pavel Kuzmich', author_email='pavelkuz99@outlook.com', description='Pure Python command-line calculator', From 95d7d4b8a609b50ff0e0ec715c1d90dd12e5c599 Mon Sep 17 00:00:00 2001 From: Pavel Kuzmich <45352360+pavelkuz99@users.noreply.github.com> Date: Wed, 19 Dec 2018 05:20:11 +0300 Subject: [PATCH 10/23] Update pycalc.py changer result return to result print --- final_task/calculator/pycalc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/final_task/calculator/pycalc.py b/final_task/calculator/pycalc.py index 7cf3759..fcd26c8 100755 --- a/final_task/calculator/pycalc.py +++ b/final_task/calculator/pycalc.py @@ -468,7 +468,7 @@ def main(): rpn.expression = rpn.parse_expression() check.initial_check(rpn.expression) rpn_expression = rpn.convert_to_rpn(rpn.expression) - return rpn.handle_operations(rpn_expression) + print(rpn.handle_operations(rpn_expression)) except Exception as e: exit(f'ERROR: {e}') From 259cd5cf810aa7c847be714ca075c7e8da363537 Mon Sep 17 00:00:00 2001 From: Pavel Kuzmich <45352360+pavelkuz99@users.noreply.github.com> Date: Wed, 19 Dec 2018 05:26:30 +0300 Subject: [PATCH 11/23] Update pycalc.py fixed exception handling --- final_task/calculator/pycalc.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/final_task/calculator/pycalc.py b/final_task/calculator/pycalc.py index fcd26c8..56449a1 100755 --- a/final_task/calculator/pycalc.py +++ b/final_task/calculator/pycalc.py @@ -2,7 +2,6 @@ from io import StringIO from tokenize import generate_tokens import math -from sys import exit from argparse import ArgumentParser from numbers import Number from collections import namedtuple @@ -470,7 +469,7 @@ def main(): rpn_expression = rpn.convert_to_rpn(rpn.expression) print(rpn.handle_operations(rpn_expression)) except Exception as e: - exit(f'ERROR: {e}') + print(f'ERROR: {e}') if __name__ == "__main__": From caa8fc53647072fed7989c1dfcb7e2685663e8a5 Mon Sep 17 00:00:00 2001 From: Pavel Kuzmich <45352360+pavelkuz99@users.noreply.github.com> Date: Wed, 19 Dec 2018 11:57:47 +0300 Subject: [PATCH 12/23] issues fixes added RedundantParameterError class, changed converting to PRN algorithm, added error hadler to operation handler --- final_task/calculator/pycalc.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/final_task/calculator/pycalc.py b/final_task/calculator/pycalc.py index 56449a1..e86a92a 100755 --- a/final_task/calculator/pycalc.py +++ b/final_task/calculator/pycalc.py @@ -16,6 +16,11 @@ class UnknownFunctionError(Exception): def __init__(self, message): super(UnknownFunctionError, self).__init__(message) + +class RedundantParameterError(Exception): + def __init__(self, message): + super(RedundantParameterError, self).__init__(message) + class MissingParameterError(Exception): def __init__(self, message): @@ -316,7 +321,11 @@ def convert_to_rpn(self, text): self.output.append(self.stack.pop()) self.stack.append(item) elif item == ',': - pass + for element in reversed(self.stack): + if element != '(': + self.output.append(self.stack.pop()) + else: + break else: raise UnknownFunctionError(f'wrong operation "{item}"') for element in reversed(self.stack): @@ -367,7 +376,8 @@ def handle_operations(self, rpn_expression): self.stack.append(function(self.pop_one())) except IndexError: raise MissingParameterError(f'not enough operands for "{op}" operation') - + if len(self.stack) > 1: + raise RedundantParameterError('function takes more parameters that it should') return self.stack[0] @staticmethod From b3cefec091c8e67a9bf3b09f213be8481bbb2cf9 Mon Sep 17 00:00:00 2001 From: Pavel Kuzmich <45352360+pavelkuz99@users.noreply.github.com> Date: Wed, 19 Dec 2018 12:02:23 +0300 Subject: [PATCH 13/23] Delete __init__.py --- final_task/calculator/__init__.py | 1 - 1 file changed, 1 deletion(-) delete mode 100644 final_task/calculator/__init__.py diff --git a/final_task/calculator/__init__.py b/final_task/calculator/__init__.py deleted file mode 100644 index 8b13789..0000000 --- a/final_task/calculator/__init__.py +++ /dev/null @@ -1 +0,0 @@ - From 79db0f1def408a9937c00aacba7606f010414581 Mon Sep 17 00:00:00 2001 From: Pavel Kuzmich <45352360+pavelkuz99@users.noreply.github.com> Date: Wed, 19 Dec 2018 12:03:53 +0300 Subject: [PATCH 14/23] Create __init__.py --- final_task/calculator/__init__.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 final_task/calculator/__init__.py diff --git a/final_task/calculator/__init__.py b/final_task/calculator/__init__.py new file mode 100644 index 0000000..d0c1902 --- /dev/null +++ b/final_task/calculator/__init__.py @@ -0,0 +1 @@ +"""Init file""" From af6c68b78c881cd0c070aa45be7fdddd80285128 Mon Sep 17 00:00:00 2001 From: Pavel Kuzmich <45352360+pavelkuz99@users.noreply.github.com> Date: Wed, 19 Dec 2018 13:20:51 +0300 Subject: [PATCH 15/23] Codestyle and implicit multiplication fixes --- final_task/calculator/pycalc.py | 41 +++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/final_task/calculator/pycalc.py b/final_task/calculator/pycalc.py index e86a92a..b634a04 100755 --- a/final_task/calculator/pycalc.py +++ b/final_task/calculator/pycalc.py @@ -16,7 +16,7 @@ class UnknownFunctionError(Exception): def __init__(self, message): super(UnknownFunctionError, self).__init__(message) - + class RedundantParameterError(Exception): def __init__(self, message): super(RedundantParameterError, self).__init__(message) @@ -247,19 +247,31 @@ def add_implicit_multiply(self, tokens_list): Returns resolved list """ resolved_list = [] + + def add_mult_sign(): + resolved_list.append('*') + resolved_list.append(token) + for index, token in enumerate(tokens_list): prev = tokens_list[index - 1] if index == 0: resolved_list.append(token) + elif token in self.prefix_ops and self.is_num(prev): + add_mult_sign() + elif token in self.const_values and self.is_num(prev): + add_mult_sign() + elif token in self.prefix_ops and self.is_num(prev): + add_mult_sign() + elif token in self.const_values and prev in self.const_values: + add_mult_sign() + elif token in self.prefix_ops and prev == ')': + add_mult_sign() elif self.is_num(token) and prev == ')': - resolved_list.append('*') - resolved_list.append(token) + add_mult_sign() elif token == '(' and prev == ')': - resolved_list.append('*') - resolved_list.append(token) + add_mult_sign() elif token == '(' and self.is_num(prev): - resolved_list.append('*') - resolved_list.append(token) + add_mult_sign() else: resolved_list.append(token) return resolved_list @@ -282,12 +294,23 @@ def resolve_unary(self, tokens_list): else: resolved_list.append(token) return resolved_list - - @staticmethod + + def resolve_double_const(self, some_sting): + """ + Resolves constant values standing together + """ + for const1 in list(self.const_values.keys()): + a = const1 + for const2 in list(self.const_values.keys()): + b = const2 + some_sting = some_sting.replace(f'{a}{b}', f'{a} {b}') + return some_sting + def create_tokens_list(some_string): """ Creates tokens list from math expressions string """ + some_string = self.resolve_double_const(some_string) line = generate_tokens(StringIO(some_string).readline) return [token[1] for token in line if token[1]] From 2d69bb8069e47c3e5e2a18ac07e8cc256e092c64 Mon Sep 17 00:00:00 2001 From: Pavel Kuzmich <45352360+pavelkuz99@users.noreply.github.com> Date: Wed, 19 Dec 2018 13:27:00 +0300 Subject: [PATCH 16/23] issue fix Fixed issue with create_tokens_list() function --- final_task/calculator/pycalc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/final_task/calculator/pycalc.py b/final_task/calculator/pycalc.py index b634a04..a3a8a62 100755 --- a/final_task/calculator/pycalc.py +++ b/final_task/calculator/pycalc.py @@ -306,7 +306,7 @@ def resolve_double_const(self, some_sting): some_sting = some_sting.replace(f'{a}{b}', f'{a} {b}') return some_sting - def create_tokens_list(some_string): + def create_tokens_list(self, some_string): """ Creates tokens list from math expressions string """ From 294cffeca6c8315f940f95475269615d9d8dc6c5 Mon Sep 17 00:00:00 2001 From: Pavel Kuzmich <45352360+pavelkuz99@users.noreply.github.com> Date: Wed, 19 Dec 2018 13:29:49 +0300 Subject: [PATCH 17/23] Codestyle fixes --- final_task/calculator/pycalc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/final_task/calculator/pycalc.py b/final_task/calculator/pycalc.py index a3a8a62..bdd9382 100755 --- a/final_task/calculator/pycalc.py +++ b/final_task/calculator/pycalc.py @@ -294,7 +294,7 @@ def resolve_unary(self, tokens_list): else: resolved_list.append(token) return resolved_list - + def resolve_double_const(self, some_sting): """ Resolves constant values standing together @@ -305,7 +305,7 @@ def resolve_double_const(self, some_sting): b = const2 some_sting = some_sting.replace(f'{a}{b}', f'{a} {b}') return some_sting - + def create_tokens_list(self, some_string): """ Creates tokens list from math expressions string From 965b6c8df874e8925c5168b98a73f017dc8e06cb Mon Sep 17 00:00:00 2001 From: Pavel Kuzmich <45352360+pavelkuz99@users.noreply.github.com> Date: Wed, 19 Dec 2018 15:18:05 +0300 Subject: [PATCH 18/23] added test file --- final_task/calculator/test.py | 140 ++++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 final_task/calculator/test.py diff --git a/final_task/calculator/test.py b/final_task/calculator/test.py new file mode 100644 index 0000000..882f53e --- /dev/null +++ b/final_task/calculator/test.py @@ -0,0 +1,140 @@ +from unittest import TestCase +from calculator import pycalc +import math + + +class TestRPN(TestCase): + def setUp(self): + self.rpn = pycalc.RPN() + + def test_clear_stack(self): + self.rpn.stack = ['3', '2', '*'] + self.rpn.stack.append('10') + self.rpn.clear_stack() + self.assertEqual(self.rpn.stack, []) + + def test_is_num(self): + self.assertEqual(self.rpn.is_num('5.0'), True) + self.assertEqual(self.rpn.is_num('.3'), True) + self.assertEqual(self.rpn.is_num('11.8'), True) + self.assertEqual(self.rpn.is_num('a'), False) + self.assertEqual(self.rpn.is_num('sin'), False) + + def test_unary_minus(self): + self.assertEqual(self.rpn.unary_minus(5), -5) + self.assertEqual(self.rpn.unary_minus(-5.6), 5.6) + self.assertEqual(self.rpn.unary_minus(-.8), 0.8) + + def test_unary_plus(self): + self.assertEqual(self.rpn.unary_plus(6), 6) + self.assertEqual(self.rpn.unary_plus(-88.6), -88.6) + self.assertEqual(self.rpn.unary_plus(.1), 0.1) + + def test_factorial(self): + self.assertEqual(self.rpn.factorial(10), math.factorial(10)) + with self.assertRaises(ValueError): + self.rpn.factorial(-13) + with self.assertRaises(ValueError): + self.rpn.factorial(666.6) + + def test_logarithm(self): + self.assertEqual(self.rpn.logarithm(8, 2), math.log(8, 2)) + with self.assertRaises(ZeroDivisionError): + self.rpn.logarithm(8, 1) + with self.assertRaises(ValueError): + self.rpn.logarithm(-8, -2) + + def test_logarithm_e(self): + self.assertEqual(self.rpn.logarithm_e(13), math.log(13)) + with self.assertRaises(ValueError): + self.rpn.logarithm_e(-20) + + def test_resolve_log(self): + self.rpn.tokens = ['log', '(', '8', ',', '2', ')'] + self.rpn.resolve_log() + self.assertEqual(self.rpn.tokens[0], 'log') + self.rpn.tokens = ['log', '(', '8', ')'] + self.rpn.resolve_log() + self.assertEqual(self.rpn.tokens[0], 'ln') + + def test_logarithm_two(self): + self.assertEqual(self.rpn.logarithm_two(8), math.log2(8)) + with self.assertRaises(ValueError): + self.rpn.logarithm_two(-666) + + def test_logarithm_ten(self): + self.assertEqual(self.rpn.logarithm_ten(8), math.log10(8)) + with self.assertRaises(ValueError): + self.rpn.logarithm_ten(-666) + + def test_power(self): + self.assertEqual(self.rpn.power(2, 4), math.pow(2, 4)) + self.assertEqual(self.rpn.power(10, 2), math.pow(10, 2)) + self.assertEqual(self.rpn.power(3, -2), math.pow(3, -2)) + with self.assertRaises(ValueError): + self.rpn.power(-2, 1.5) + + def test_square_root(self): + self.assertEqual(self.rpn.square_root(16), math.sqrt(16)) + self.assertEqual(self.rpn.square_root(101.1), math.sqrt(101.1)) + with self.assertRaises(ValueError): + self.rpn.square_root(-2) + + def test_divide(self): + self.assertEqual(self.rpn.divide(18, -2.5), 18 / -2.5) + with self.assertRaises(ZeroDivisionError): + self.rpn.divide(-2, 0) + + def test_int_divide(self): + self.assertEqual(self.rpn.int_divide(1813, 22), 1813 // 22) + with self.assertRaises(ZeroDivisionError): + self.rpn.int_divide(-2, 0) + + def test_division_rest(self): + self.assertEqual(self.rpn.division_rest(131, 0.8), 131 % 0.8) + with self.assertRaises(ZeroDivisionError): + self.rpn.division_rest(13, 0) + + def test_add_implicit_multiply(self): + token_list1 = ['2', '(', '10', '+', '1'')'] + token_list2 = ['8', 'sin', '(', '10', '+', '1'')'] + token_list3 = ['(', '3', ')', '(', '10', '+', '1', ')'] + token_list4 = ['e', 'pi'] + self.assertEqual(self.rpn.add_implicit_multiply(token_list1), ['2', '*', '(', '10', '+', '1'')']) + self.assertEqual(self.rpn.add_implicit_multiply(token_list2), ['8', '*', 'sin', '(', '10', '+', '1'')']) + self.assertEqual(self.rpn.add_implicit_multiply(token_list3), ['(', '3', ')', '*', '(', '10', '+', '1', ')']) + self.assertEqual(self.rpn.add_implicit_multiply(token_list4), ['e', '*', 'pi']) + + def test_resolve_unary(self): + token_list1 = ['+', '13'] + token_list2 = ['-', 'sin', '(', 'pi', ')'] + self.assertEqual(self.rpn.resolve_unary(token_list1), ['plus', '13']) + self.assertEqual(self.rpn.resolve_unary(token_list2), ['minus', 'sin', '(', 'pi', ')']) + + def test_resolve_double_const(self): + self.assertEqual(self.rpn.resolve_double_const('epi + pitau'), 'e pi + pi tau') + + def test_create_tokens_list(self): + result1 = ['3', '+', '2', '-', 'log', '(', '8', ',', '2', ')'] + self.assertEqual(self.rpn.create_tokens_list('3+2-log(8,2)'), result1) + result2 = ['sin', '(', 'pi', '/', '2', ')'] + self.assertEqual(self.rpn.create_tokens_list('sin(pi/2)'), result2) + + def test_convert_to_rpn(self): + expression = '-sin(pi/2)' + self.assertEqual(self.rpn.convert_to_rpn(expression), ['pi', '2', '/', 'sin', 'minus']) + expression = 'sen(pi/2)' + with self.assertRaises(pycalc.UnknownFunctionError): + self.rpn.convert_to_rpn(expression) + + def test_pop_one(self): + self.rpn.stack = [1, 2, 3, 4] + self.assertEqual(self.rpn.pop_one(), 4) + + def test_pop_two(self): + self.rpn.stack = [1, 2, 3, 4] + self.assertEqual(self.rpn.pop_one(), 4, 3) + + def test_handle_operations(self): + rpn_expression1 = ['pi', '2', '/', 'sin', 'minus'] + self.assertEqual(self.rpn.handle_operations(rpn_expression1), -1) From 37143590e9f6953117fc37750e54ae956c0811b9 Mon Sep 17 00:00:00 2001 From: Pavel Kuzmich <45352360+pavelkuz99@users.noreply.github.com> Date: Wed, 19 Dec 2018 15:57:09 +0300 Subject: [PATCH 19/23] added some tests --- final_task/calculator/test.py | 43 +++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/final_task/calculator/test.py b/final_task/calculator/test.py index 882f53e..50a2b6e 100644 --- a/final_task/calculator/test.py +++ b/final_task/calculator/test.py @@ -138,3 +138,46 @@ def test_pop_two(self): def test_handle_operations(self): rpn_expression1 = ['pi', '2', '/', 'sin', 'minus'] self.assertEqual(self.rpn.handle_operations(rpn_expression1), -1) + + +class TestCheck(TestCase): + def setUp(self): + self.rpn = pycalc.RPN() + self.check = pycalc.Check() + + def test_check_for_numbers(self): + expression1 = 'sin+cos' + expression2 = '3+2' + with self.assertRaises(pycalc.MissingParameterError): + self.check.check_for_numbers(expression1) + self.check.check_for_numbers(expression2) + + def test_check_parentheses(self): + expression1 = '8*(3+2))' + expression2 = '(8*(3+2))' + expression3 = '(8*(3+2)' + with self.assertRaises(pycalc.UnbalancedParenthesesError): + self.check.check_parentheses(expression1) + self.check.check_parentheses(expression2) + with self.assertRaises(pycalc.UnbalancedParenthesesError): + self.check.check_parentheses(expression3) + + def test_check_for_symbols(self): + expression1 = '8#(3+2))' + expression2 = '8+(3~2))' + expression3 = '3 + 2' + with self.assertRaises(pycalc.UnknownSymbolError): + self.check.check_for_symbols(expression1) + with self.assertRaises(pycalc.UnknownSymbolError): + self.check.check_for_symbols(expression2) + self.check.check_for_symbols(expression3) + + def test_check_spaces(self): + expression1 = '1 2' + expression2 = '8 > = 7' + expression3 = '11 + sin(13)' + with self.assertRaises(pycalc.UnexpectedSpaceError): + self.check.check_spaces(expression1) + with self.assertRaises(pycalc.UnexpectedSpaceError): + self.check.check_spaces(expression2) + self.check.check_spaces(expression3) From 02807ca8009098b6cfd8fea86fdda09814c3c7d0 Mon Sep 17 00:00:00 2001 From: Pavel Kuzmich <45352360+pavelkuz99@users.noreply.github.com> Date: Wed, 19 Dec 2018 16:01:05 +0300 Subject: [PATCH 20/23] fixed import --- final_task/calculator/pycalc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/final_task/calculator/pycalc.py b/final_task/calculator/pycalc.py index bdd9382..54370e5 100755 --- a/final_task/calculator/pycalc.py +++ b/final_task/calculator/pycalc.py @@ -3,7 +3,7 @@ from tokenize import generate_tokens import math from argparse import ArgumentParser -from numbers import Number +import numbers.Number from collections import namedtuple @@ -79,7 +79,7 @@ def __init__(self): '>=': sign(0, lambda digit1, digit2: digit1 >= digit2), '>': sign(0, lambda digit1, digit2: digit1 > digit2) } - values = {attr: getattr(math, attr) for attr in dir(math) if isinstance(getattr(math, attr), Number)} + values = {attr: getattr(math, attr) for attr in dir(math) if isinstance(getattr(math, attr), numbers.Number)} self.const_values = values self.all_ops = {**self.postfix_ops, **self.prefix_ops, **self.signs} From 599d0e660317171be3f298ef3788946c903db35d Mon Sep 17 00:00:00 2001 From: Pavel Kuzmich <45352360+pavelkuz99@users.noreply.github.com> Date: Wed, 19 Dec 2018 16:03:51 +0300 Subject: [PATCH 21/23] Update pycalc.py --- final_task/calculator/pycalc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/final_task/calculator/pycalc.py b/final_task/calculator/pycalc.py index 54370e5..bdd9382 100755 --- a/final_task/calculator/pycalc.py +++ b/final_task/calculator/pycalc.py @@ -3,7 +3,7 @@ from tokenize import generate_tokens import math from argparse import ArgumentParser -import numbers.Number +from numbers import Number from collections import namedtuple @@ -79,7 +79,7 @@ def __init__(self): '>=': sign(0, lambda digit1, digit2: digit1 >= digit2), '>': sign(0, lambda digit1, digit2: digit1 > digit2) } - values = {attr: getattr(math, attr) for attr in dir(math) if isinstance(getattr(math, attr), numbers.Number)} + values = {attr: getattr(math, attr) for attr in dir(math) if isinstance(getattr(math, attr), Number)} self.const_values = values self.all_ops = {**self.postfix_ops, **self.prefix_ops, **self.signs} From b60409f87e5bdcbe5f5279f1108a64d4f5ec69b2 Mon Sep 17 00:00:00 2001 From: Pavel Kuzmich <45352360+pavelkuz99@users.noreply.github.com> Date: Wed, 19 Dec 2018 16:42:14 +0300 Subject: [PATCH 22/23] added some tests --- final_task/calculator/test.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/final_task/calculator/test.py b/final_task/calculator/test.py index 50a2b6e..1daff75 100644 --- a/final_task/calculator/test.py +++ b/final_task/calculator/test.py @@ -100,10 +100,16 @@ def test_add_implicit_multiply(self): token_list2 = ['8', 'sin', '(', '10', '+', '1'')'] token_list3 = ['(', '3', ')', '(', '10', '+', '1', ')'] token_list4 = ['e', 'pi'] + token_list5 = ['(', '3', ')', '10'] + token_list6 = ['(', '3', ')', 'sin', '(', '1', ')'] + token_list7 = ['6', 'pi'] self.assertEqual(self.rpn.add_implicit_multiply(token_list1), ['2', '*', '(', '10', '+', '1'')']) self.assertEqual(self.rpn.add_implicit_multiply(token_list2), ['8', '*', 'sin', '(', '10', '+', '1'')']) self.assertEqual(self.rpn.add_implicit_multiply(token_list3), ['(', '3', ')', '*', '(', '10', '+', '1', ')']) self.assertEqual(self.rpn.add_implicit_multiply(token_list4), ['e', '*', 'pi']) + self.assertEqual(self.rpn.add_implicit_multiply(token_list5), ['(', '3', ')', '*', '10']) + self.assertEqual(self.rpn.add_implicit_multiply(token_list6), ['(', '3', ')', '*', 'sin', '(', '1', ')']) + self.assertEqual(self.rpn.add_implicit_multiply(token_list7), ['6', '*', 'pi']) def test_resolve_unary(self): token_list1 = ['+', '13'] @@ -136,8 +142,12 @@ def test_pop_two(self): self.assertEqual(self.rpn.pop_one(), 4, 3) def test_handle_operations(self): - rpn_expression1 = ['pi', '2', '/', 'sin', 'minus'] - self.assertEqual(self.rpn.handle_operations(rpn_expression1), -1) + expression1 = '3 + 2 1' + rpn_expression1 = self.rpn.convert_to_rpn(expression1) + with self.assertRaises(pycalc.RedundantParameterError): + self.rpn.handle_operations(rpn_expression1) + rpn_expression2 = ['pi', '2', '/', 'sin', 'minus'] + self.assertEqual(self.rpn.handle_operations(rpn_expression2), -1) class TestCheck(TestCase): @@ -176,8 +186,14 @@ def test_check_spaces(self): expression1 = '1 2' expression2 = '8 > = 7' expression3 = '11 + sin(13)' + expression4 = '5 / / 88' + expression5 = '(88) .3' with self.assertRaises(pycalc.UnexpectedSpaceError): self.check.check_spaces(expression1) with self.assertRaises(pycalc.UnexpectedSpaceError): self.check.check_spaces(expression2) self.check.check_spaces(expression3) + with self.assertRaises(pycalc.UnexpectedSpaceError): + self.check.check_spaces(expression4) + with self.assertRaises(pycalc.UnexpectedSpaceError): + self.check.check_spaces(expression5) From e376b35d2d97d7acbb4c97aad4f97634e83e4f04 Mon Sep 17 00:00:00 2001 From: Pavel Kuzmich <45352360+pavelkuz99@users.noreply.github.com> Date: Wed, 19 Dec 2018 17:02:32 +0300 Subject: [PATCH 23/23] fixed tests --- final_task/calculator/test.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/final_task/calculator/test.py b/final_task/calculator/test.py index 1daff75..4c0ad4c 100644 --- a/final_task/calculator/test.py +++ b/final_task/calculator/test.py @@ -146,8 +146,6 @@ def test_handle_operations(self): rpn_expression1 = self.rpn.convert_to_rpn(expression1) with self.assertRaises(pycalc.RedundantParameterError): self.rpn.handle_operations(rpn_expression1) - rpn_expression2 = ['pi', '2', '/', 'sin', 'minus'] - self.assertEqual(self.rpn.handle_operations(rpn_expression2), -1) class TestCheck(TestCase):