From d2982419a9387620ee4d9ae516de8b9a111b5d28 Mon Sep 17 00:00:00 2001 From: Roma Date: Sun, 16 Dec 2018 21:54:13 +0300 Subject: [PATCH 01/30] Pure-python calculator. --- pycalc | 21 +++ pycalc_src/__init__.py | 1 + pycalc_src/calculator.py | 243 +++++++++++++++++++++++++ pycalc_src/exceptions.py | 30 ++++ pycalc_src/operators.py | 65 +++++++ pycalc_src/preprocessing.py | 58 ++++++ setup.py | 11 ++ tests/.DS_Store | Bin 0 -> 6148 bytes tests/__init__.py | 0 tests/test_calculator.py | 350 ++++++++++++++++++++++++++++++++++++ tests/test_preprocessing.py | 72 ++++++++ 11 files changed, 851 insertions(+) create mode 100755 pycalc create mode 100644 pycalc_src/__init__.py create mode 100644 pycalc_src/calculator.py create mode 100644 pycalc_src/exceptions.py create mode 100644 pycalc_src/operators.py create mode 100644 pycalc_src/preprocessing.py create mode 100644 setup.py create mode 100644 tests/.DS_Store create mode 100644 tests/__init__.py create mode 100644 tests/test_calculator.py create mode 100644 tests/test_preprocessing.py diff --git a/pycalc b/pycalc new file mode 100755 index 0000000..34a8874 --- /dev/null +++ b/pycalc @@ -0,0 +1,21 @@ +#!/usr/local/bin/python3 + +"""Pure-python command-line calculator.""" + +import argparse + +from pycalc_src import Calculator + +def main(): + """Docstring.""" + + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('EXPRESSION', help='expression string to evaluate') + args = parser.parse_args() + + pycalc = Calculator(args.EXPRESSION) + print(pycalc.calculate()) + + +if __name__ == '__main__': + main() diff --git a/pycalc_src/__init__.py b/pycalc_src/__init__.py new file mode 100644 index 0000000..e3fcb49 --- /dev/null +++ b/pycalc_src/__init__.py @@ -0,0 +1 @@ +from pycalc_src.calculator import Calculator diff --git a/pycalc_src/calculator.py b/pycalc_src/calculator.py new file mode 100644 index 0000000..9557ff5 --- /dev/null +++ b/pycalc_src/calculator.py @@ -0,0 +1,243 @@ + +from pycalc_src.exceptions import CalculatorError + +from pycalc_src.operators import OPERATORS +from pycalc_src.operators import CONSTANTS +from pycalc_src.operators import UNARY_OPERATORS +from pycalc_src.operators import COMPARISON_SYMBOLS + +from pycalc_src.preprocessing import prepare_expression + +from numbers import Number + +class Calculator: + """Docstring.""" + + def __init__(self, expression): + """Docstring.""" + self.expression = expression + self.number = '' + self.operator = '' + self.unary_operator = '' + self.rpn = [] + self.stack = [] + + def _process_digit(self, index, symbol): + + if self.expression[index - 1] == ' ' and self.number: + raise CalculatorError('invalid syntax') + self.number += symbol + + def _process_number_and_constant(self): + + if self.unary_operator: + self.unary_operator = self._replace_unary_operator(self.unary_operator) + + if self.number: + self.rpn.append(self._convert_to_number('{}{}'.format(self.unary_operator, + self.number))) + self.number = '' + + if self.operator in CONSTANTS: + if self.unary_operator == '-': + self.rpn.append(0 - CONSTANTS[self.operator]) + else: + self.rpn.append(CONSTANTS[self.operator]) + self.operator = '' + + self.unary_operator = '' + + def _process_operator(self): + + if self.unary_operator: + self.stack.append(self.unary_operator) + + if self.operator: + if self.operator not in OPERATORS: + raise CalculatorError('operator not supported') + self.stack.append(self.operator) + + self.unary_operator = '' + self.operator = '' + + def _process_stack(self, symbol): + + while self.stack: + if self.stack[-1] == symbol == '^': + break + + if OPERATORS[symbol].priority <= OPERATORS[self.stack[-1]].priority: + self.rpn.append(self.stack.pop()) + else: + break + + self.stack.append(symbol) + + def _process_comparison(self, index, symbol): + + self._process_number_and_constant() + + if self.stack and self.stack[-1] in COMPARISON_SYMBOLS: + if self.expression[index - 1] == ' ': + raise CalculatorError('unexpected whitespace') + self.stack[-1] += symbol + else: + while self.stack: + self.rpn.append(self.stack.pop()) + + self.stack.append(symbol) + + def _process_brackets_and_comma(self, symbol): + + if symbol == ',': + self._process_number_and_constant() + while self.stack: + if OPERATORS[symbol].priority < OPERATORS[self.stack[-1]].priority: + self.rpn.append(self.stack.pop()) + else: + break + self.stack.append(symbol) + elif symbol == '(': + if self.number: + self._process_number_and_constant() + self.stack.append('*') + else: + self._process_operator() + self.stack.append(symbol) + elif symbol == ')': + self._process_number_and_constant() + while self.stack: + element = self.stack.pop() + if element == '(': + break + self.rpn.append(element) + + def _is_unary_operator(self, index, symbol): + + if symbol not in UNARY_OPERATORS: + return False + if index <= len(self.expression): + prev_symbol = self.expression[index - 1] + if index == 0 or (prev_symbol in OPERATORS and prev_symbol != ')' + or prev_symbol in COMPARISON_SYMBOLS): + return True + return False + + def _is_floordiv(self, index, symbol): + if index <= len(self.expression): + return symbol == self.expression[index - 1] == '/' + return False + + def _process_expression(self): + for index, symbol in enumerate(self.expression): + + if self.operator in CONSTANTS: + self._process_number_and_constant() + + if symbol in COMPARISON_SYMBOLS: + self._process_comparison(index, symbol) + continue + + if symbol.isdigit() and self.operator: + self.operator += symbol + elif symbol.isdigit() or symbol == '.': + self._process_digit(index, symbol) + elif symbol in ('(', ',', ')'): + self._process_brackets_and_comma(symbol) + elif symbol in OPERATORS: + if self.stack and self._is_floordiv(index, symbol): + self.stack[-1] += symbol + continue + + if self._is_unary_operator(index, symbol): + self.unary_operator = UNARY_OPERATORS[symbol] + continue + + self._process_number_and_constant() + self._process_stack(symbol) + elif symbol.isalpha() or symbol == '=': + self.operator += symbol + + self._process_number_and_constant() + self.rpn.extend(reversed(self.stack)) + + if not self.rpn: + raise CalculatorError('not enough data to calculate') + + del self.stack[:] + + def _calculate_operator(self, operator): + + operator_params = OPERATORS[operator] + + real_params_count = operator_params.params_quantity + if real_params_count == 3: # 'round' , 'log', 'hypot', 'atan2' + if self.stack and self.stack[-1] == ',': + self.stack.pop() + real_params_count = 2 + else: + real_params_count = 1 + + if len(self.stack) < real_params_count: + raise CalculatorError("not enough operand's for function {}".format(operator)) + elif self.stack and not isinstance(self.stack[-1], Number): + raise CalculatorError("incorrect operand's for function {}".format(operator)) + + if real_params_count == 1: + operand = self.stack.pop() + self._calculate_result(operator_params.function, operand) + elif real_params_count == 2: + second_operand = self.stack.pop() + first_operand = self.stack.pop() + self._calculate_result(operator_params.function, first_operand, second_operand) + + def _calculate_result(self, function, first_operand, second_operand=None): + + try: + if second_operand is None: + result = function(first_operand) + else: + result = function(first_operand, second_operand) + except ZeroDivisionError as e: + raise CalculatorError(e) + except ArithmeticError as e: + raise CalculatorError(e) + except Exception as e: + raise CalculatorError(e) + else: + self.stack.append(result) + + def _calculate_rpn(self): + + for item in self.rpn: + if item == ',': + self.stack.append(item) + elif item in UNARY_OPERATORS.values(): + unary_operator = self._replace_unary_operator(item) + self.stack.append(self._convert_to_number('{}1'.format(unary_operator))) + self._calculate_operator('*') + elif item in OPERATORS: + self._calculate_operator(item) + else: + self.stack.append(item) + + def _replace_unary_operator(self, unary_operator): + + for key, value in UNARY_OPERATORS.items(): + if value == unary_operator: + return key + + def _convert_to_number(self, number): + """Docstring.""" + if not isinstance(number, str): + return 0 + return float(number) if '.' in number else int(number) + + def calculate(self): + """Docstring.""" + self.expression = prepare_expression(self.expression) + + self._process_expression() + self._calculate_rpn() + + return self.stack[-1] diff --git a/pycalc_src/exceptions.py b/pycalc_src/exceptions.py new file mode 100644 index 0000000..59f9719 --- /dev/null +++ b/pycalc_src/exceptions.py @@ -0,0 +1,30 @@ +import sys + + +class BaseCalculatorException(Exception): + """Docstring.""" + + def __init__(self, message=None): + """Docstring.""" + + if message is None: + message = 'an error occured while working pycalc' + self.message = 'ERROR: {}'.format(message) + + print(self.message) + + #sys.exit(1) + + +class CalculatorError(BaseCalculatorException): + """Docstring.""" + def __init__(self, message=None): + """Docstring.""" + super().__init__(message) + + +class PreprocessingError(BaseCalculatorException): + """Docstring.""" + def __init__(self, message=None): + """Docstring.""" + super().__init__(message) diff --git a/pycalc_src/operators.py b/pycalc_src/operators.py new file mode 100644 index 0000000..05eb8c0 --- /dev/null +++ b/pycalc_src/operators.py @@ -0,0 +1,65 @@ +"""Operators, constants, unary operators and comparison symbol's for pycalc.""" + +import operator +import builtins +import math + +from collections import namedtuple + + +UNARY_OPERATORS = {'-': '-@', '+': '+@'} + + +COMPARISON_SYMBOLS = ('!', '<', '>', '=') + + +OPERATOR = namedtuple('OPERATOR', 'priority function params_quantity') + + +OPERATORS = { + '+': OPERATOR(1, operator.add, 2), + '-': OPERATOR(1, operator.sub, 2), + '*': OPERATOR(2, operator.mul, 2), + '/': OPERATOR(2, operator.truediv, 2), + '//': OPERATOR(2, operator.floordiv, 2), + '%': OPERATOR(2, operator.mod, 2), + '^': OPERATOR(3, operator.pow, 2), + + 'sin': OPERATOR(4, math.sin, 1), + 'cos': OPERATOR(4, math.cos, 1), + 'asin': OPERATOR(4, math.asin, 1), + 'acos': OPERATOR(4, math.acos, 1), + 'sinh': OPERATOR(4, math.sinh, 1), + 'cosh': OPERATOR(4, math.cosh, 1), + 'asinh': OPERATOR(4, math.asinh, 1), + 'acosh': OPERATOR(4, math.acosh, 1), + 'tanh': OPERATOR(4, math.tanh, 1), + 'atanh': OPERATOR(4, math.atanh, 1), + 'tan': OPERATOR(4, math.tan, 1), + 'atan': OPERATOR(4, math.atan, 1), + 'hypot': OPERATOR(4, math.hypot, 3), + 'atan2': OPERATOR(4, math.atan2, 3), + 'exp': OPERATOR(4, math.exp, 1), + 'expm1': OPERATOR(4, math.expm1, 1), + 'log10': OPERATOR(4, math.log10, 1), + 'log2': OPERATOR(4, math.log2, 1), + 'log1p': OPERATOR(4, math.log1p, 1), + 'sqrt': OPERATOR(4, math.sqrt, 1), + 'abs': OPERATOR(4, builtins.abs, 1), + 'round': OPERATOR(4, builtins.round, 3), + 'log': OPERATOR(4, math.log, 3), + + '<': OPERATOR(0, operator.lt, 2), + '<=': OPERATOR(0, operator.le, 2), + '==': OPERATOR(0, operator.eq, 2), + '!=': OPERATOR(0, operator.ne, 2), + '>=': OPERATOR(0, operator.ge, 2), + '>': OPERATOR(0, operator.gt, 2), + ',': OPERATOR(0, None, 0), + '(': OPERATOR(0, None, 0), + ')': OPERATOR(5, None, 0), + '-@': OPERATOR(2, None, 0), + '+@': OPERATOR(2, None, 0) +} + +CONSTANTS = {a: getattr(math, a) for a in dir(math) if isinstance(getattr(math, a), float)} diff --git a/pycalc_src/preprocessing.py b/pycalc_src/preprocessing.py new file mode 100644 index 0000000..2781058 --- /dev/null +++ b/pycalc_src/preprocessing.py @@ -0,0 +1,58 @@ + +from pycalc_src.exceptions import PreprocessingError + +from pycalc_src.operators import OPERATORS +from pycalc_src.operators import CONSTANTS + + +def _preprocessing(expression): + + if not expression: + raise PreprocessingError('expression is empty') + + if not isinstance(expression, str): + raise PreprocessingError('expression is not a string') + + if expression.count('(') != expression.count(')'): + raise PreprocessingError('brackets are not balanced') + + expression = expression.lower() + + if not _is_operators_available(expression): + raise PreprocessingError('there are no operators in the expression') + + expression = expression.replace('**', '^') + + expression = _clean_repeatable_operators(expression) + + return expression + + +def _is_operators_available(expression): + for statement in OPERATORS: + if statement in expression: + return True + + for statement in CONSTANTS: + if statement in expression: + return True + return False + + +def _clean_repeatable_operators(expression): + repeatable_operators = {'+-': '-', '--': '+', '++': '+', '-+': '-'} + + while True: + old_exp = expression + for old, new in repeatable_operators.items(): + expression = expression.replace(old, new) + if old_exp == expression: + break + + return expression + + +def prepare_expression(expression): + """Docstring.""" + + return _preprocessing(expression) diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..b66c2a3 --- /dev/null +++ b/setup.py @@ -0,0 +1,11 @@ +from setuptools import setup, find_packages + +setup( + name="pycalc", + version="1.0", + packages=['pycalc_src'], + scripts=['pycalc'], + author="roman.yastremski", + author_email="roman.yastremski@gmail.com", + description="Pure-python command-line calculator." +) \ No newline at end of file diff --git a/tests/.DS_Store b/tests/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..24808b6a01bcd06f6d4dd11250be39210ab7bff2 GIT binary patch literal 6148 zcmeHKyG{c^44j1&k!T_+$}Opo_=8oH6x4hGkU)3RA(9ky*YPd+N*F&ti4G(x3N$0x zv+J{Wb5opS0A;>=yar|frZi)`nH$5Nn~iXikwT{Nj61CHfR27Q%KkoJwddI42^;#u z`i>`U(c=YIc*g5Z*RNMyzhb`q*gfs?-NBV01*Cu!kOERb3j9_9>%GMEELdI&NC7GE zrGR}OD$Teh55fNFpz{%cJYm?uwa*gdY5{Uf9)cMmN|chLwB(3UqMY%Xc`bPej&c;g zndjuqB`1{Pw=-Tn9i;`!O93fxtiWk(XV(9hv>)dG<09>(fE4&w3Y5v>b}{EGWp5q5 xob}pDyQh7Ext>nvt(fesm>X-w7YBLO)_mTQhhQ&f+{?-OBVf7+Qs6HX_yFBk9;pBT literal 0 HcmV?d00001 diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_calculator.py b/tests/test_calculator.py new file mode 100644 index 0000000..f2e9ed5 --- /dev/null +++ b/tests/test_calculator.py @@ -0,0 +1,350 @@ +"""Unittest for class Calculator.""" + +import unittest + +import operator +import builtins +import math + +from collections import namedtuple + +from pycalc_src.calculator import Calculator +from pycalc_src.exceptions import BaseCalculatorException + + +class TestStringMethods(unittest.TestCase): + """Docstring.""" + + def test_process_digit__valid_expressions(self): + """Docstring.""" + valid_expression = namedtuple('valid_expression', 'expression index symbol result') + valid_expressions = [valid_expression('5', 0, '5', '5'), + valid_expression(' .', 1, '.', '.'), + valid_expression('1 ', 0, '1', '1') + ] + + for expression in valid_expressions: + calc = Calculator(expression.expression) + calc._process_digit(expression.index, expression.symbol) + + self.assertEqual(calc.number, expression.result) + + def test_process_digit__invalid_expressions(self): + """Docstring.""" + + expression = '1 2 3 4' + + calc = Calculator(expression) + calc.number = '1' + + with self.assertRaises(BaseCalculatorException): + calc._process_digit(2, '2') + + def test_process_number_and_constant__valid_expressions(self): + """Docstring.""" + + valid_expression = namedtuple('valid_expression', 'unary_operator number operator result') + valid_expressions = [valid_expression('', '54.55', '', 54.55), + valid_expression('-@', '5', '', -5), + valid_expression('', '', 'pi', math.pi), + valid_expression('-@', '', 'e', -math.e) + ] + + for expression in valid_expressions: + calc = Calculator('') + calc.unary_operator = expression.unary_operator + calc.number = expression.number + calc.operator = expression.operator + calc._process_number_and_constant() + + self.assertEqual(calc.rpn[-1], expression.result) + + def test_process_operator__valid_expressions(self): + """Docstring.""" + + valid_expression = namedtuple('valid_expression', 'unary_operator operator result') + valid_expressions = [valid_expression('', 'sin', ['sin']), + valid_expression('-@', 'log', ['-@', 'log']) + ] + + for expression in valid_expressions: + calc = Calculator('') + calc.unary_operator = expression.unary_operator + calc.operator = expression.operator + calc._process_operator() + + self.assertEqual(calc.stack, expression.result) + + def test_process_operator__invalid_expressions(self): + """Docstring.""" + + invalid_expression = namedtuple('valid_expression', 'unary_operator operator') + invalid_expressions = [invalid_expression('', 'log100'), + invalid_expression('-@', 'sin4') + ] + + for expression in invalid_expressions: + calc = Calculator('') + calc.unary_operator = expression.unary_operator + calc.operator = expression.operator + + with self.assertRaises(BaseCalculatorException): + calc._process_operator() + + def test_process_stack__valid_expressions(self): + """Docstring.""" + + valid_expression = namedtuple('valid_expression', 'stack symbol result_stack result_rpn') + valid_expressions = [valid_expression(['^'], '^', ['^', '^'], []), + valid_expression(['*'], '+', ['+'], ['*']), + valid_expression(['-'], '/', ['-', '/'], []), + valid_expression(['sin', 'tan'], '/', ['/'], ['tan', 'sin']) + ] + + for expression in valid_expressions: + calc = Calculator('') + calc.stack = expression.stack + calc._process_stack(expression.symbol) + + self.assertEqual(calc.stack, expression.result_stack) + self.assertEqual(calc.rpn, expression.result_rpn) + + def test_process_comparison__valid_expressions(self): + """Docstring.""" + + valid_expression = namedtuple('valid_expression', 'expression stack index symbol result_stack result_rpn') + valid_expressions = [valid_expression('5 >= 4', ['>'], 3, '=', ['>='], []), + valid_expression('5+1*2 > 4', ['+', '*'], 7, '>', ['>'], ['*', '+']) + ] + + for expression in valid_expressions: + calc = Calculator(expression.expression) + calc.stack = expression.stack + calc._process_comparison(expression.index, expression.symbol) + + self.assertEqual(calc.stack, expression.result_stack) + self.assertEqual(calc.rpn, expression.result_rpn) + + def test_process_comparison__invalid_expressions(self): + """Docstring.""" + + invalid_expression = namedtuple('invalid_expression', 'expression stack index symbol') + invalid_expressions = [invalid_expression('5 > = 4', ['>'], 4, '='), + invalid_expression('5+2 = = 4', ['='], 6, '=') + ] + + for expression in invalid_expressions: + calc = Calculator(expression.expression) + calc.stack = expression.stack + + with self.assertRaises(BaseCalculatorException): + calc._process_comparison(expression.index, expression.symbol) + + def test_process_brackets_and_comma__valid_expressions(self): + """Docstring.""" + + valid_expression = namedtuple('valid_expression', 'expression stack symbol number result_stack result_rpn') + valid_expressions = [ + valid_expression('round(1.22, 4)', ['round', '('], ',', '', ['round', '(', ','], []), + valid_expression('round(1.22+2, 4)', ['round', '(', '+'], ',', '', ['round', '(', ','], ['+']), + valid_expression('2 + (4)', ['+'], '(', '', ['+', '('], []), + valid_expression('2 + 2(4)', ['+'], '(', '2', ['+', '*', '('], [2]), + valid_expression('(4 + 3 * 2)', ['(', '+', '*'], ')', '', [], ['*', '+']), + valid_expression('1 + (3 * 2)', ['+', '(', '*'], ')', '', ['+'], ['*']) + ] + + for expression in valid_expressions: + calc = Calculator(expression.expression) + calc.stack = expression.stack + calc.number = expression.number + calc._process_brackets_and_comma(expression.symbol) + + self.assertEqual(calc.stack, expression.result_stack) + self.assertEqual(calc.rpn, expression.result_rpn) + + def test_is_unary_operator__valid_expressions(self): + """Docstring.""" + + valid_expression = namedtuple('valid_expression', 'expression index symbol result') + valid_expressions = [valid_expression('-4', 0, '-', True), + valid_expression('!4', 0, '!', False), + valid_expression('-4', 4, '-', False), + valid_expression('1*-4', 2, '-', True), + valid_expression('(1*2)-4', 5, '-', False), + valid_expression('5==-5', 3, '-', True) + ] + + for expression in valid_expressions: + calc = Calculator(expression.expression) + func_result = calc._is_unary_operator(expression.index, expression.symbol) + + if expression.result: + self.assertTrue(func_result) + else: + self.assertFalse(func_result) + + def test_is_floordiv__valid_expressions(self): + """Docstring.""" + + valid_expression = namedtuple('valid_expression', 'expression index symbol result') + valid_expressions = [valid_expression('5/5', 4, '', False), + valid_expression('4//3', 2, '/', True), + valid_expression('4/3', 1, '/', False) + ] + + for expression in valid_expressions: + calc = Calculator(expression.expression) + func_result = calc._is_floordiv(expression.index, expression.symbol) + + if expression.result: + self.assertTrue(func_result) + else: + self.assertFalse(func_result) + + def test_process_expression__valid_expressions(self): + """Docstring.""" + + valid_expression = namedtuple('valid_expression', 'expression result_rpn') + valid_expressions = [valid_expression('pi', [3.141592653589793]), + valid_expression('<=', ['<=']), + valid_expression('log2()', ['log2']), + valid_expression('51.567', [51.567]), + valid_expression('round(1.233333, 2)', [1.233333, 2, ',', 'round']), + valid_expression('81//8', [81, 8 , '//']), + valid_expression('//', ['//']), + valid_expression('-100', [-100]), + valid_expression('pi*log2(1)==-1', [3.141592653589793, 1, 'log2', '*', -1, '==']) + ] + + for expression in valid_expressions: + calc = Calculator(expression.expression) + calc._process_expression() + + self.assertEqual(calc.rpn, expression.result_rpn) + + def test_process_expression__invalid_expressions(self): + """Docstring.""" + + invalid_expression = namedtuple('invalid_expression', 'expression') + invalid_expressions = [invalid_expression('not an expression') + ] + + for expression in invalid_expressions: + calc = Calculator(expression.expression) + + with self.assertRaises(BaseCalculatorException): + calc._process_expression() + + def test_calculate_operator__valid_expressions(self): + """Docstring.""" + + valid_expression = namedtuple('valid_expression', 'expression stack operator result_stack') + valid_expressions = [valid_expression('1+2', [1, 2], '+', [3]), + valid_expression('round(1.2254,2)', [1.2254, 2, ','], 'round', [1.23]), + valid_expression('log(.5)', [0.5], 'log', [-0.6931471805599453]) + ] + + for expression in valid_expressions: + calc = Calculator(expression.expression) + calc.stack = expression.stack + calc._calculate_operator(expression.operator) + + self.assertEqual(calc.stack, expression.result_stack) + + def test_calculate_operator__invalid_expressions(self): + """Docstring.""" + + invalid_expression = namedtuple('invalid_expression', 'expression stack operator') + invalid_expressions = [invalid_expression('log(.5,)', [0.5, ','], 'log'), + invalid_expression('log(.5,1,2)', [0.5, 1, 2, ',', ','], 'log') + ] + + for expression in invalid_expressions: + calc = Calculator(expression.expression) + calc.stack = expression.stack + + with self.assertRaises(BaseCalculatorException): + calc._calculate_operator(expression.operator) + + def test_calculate_result__valid_expressions(self): + """Docstring.""" + + valid_expression = namedtuple('valid_expression', + 'expression function first_operand second_operand result_stack') + valid_expressions = [valid_expression('365+635', operator.add, 365, 635, [1000]), + valid_expression('sin(1)', math.sin, 1, None, [0.8414709848078965]) + ] + + for expression in valid_expressions: + calc = Calculator(expression.expression) + calc._calculate_result(expression.function, expression.first_operand, expression.second_operand) + + self.assertEqual(calc.stack, expression.result_stack) + + def test_calculate_result__invalid_expressions(self): + """Docstring.""" + + invalid_expression = namedtuple('invalid_expression', 'expression function first_operand second_operand') + invalid_expressions = [invalid_expression('5/0', operator.truediv, 5, 0), + invalid_expression('log(-100)', math.log, -100, None), + invalid_expression('log(1,,)', math.log, 1, ',') + ] + + for expression in invalid_expressions: + calc = Calculator(expression.expression) + + with self.assertRaises(BaseCalculatorException): + calc._calculate_result(expression.function, expression.first_operand, expression.second_operand) + + def test_calculate_rpn__valid_expressions(self): + """Docstring.""" + + valid_expression = namedtuple('valid_expression', 'expression rpn result_stack') + valid_expressions = [valid_expression(',', [','], [',']), + valid_expression('-(3)', [3, '-@'], [-3]), + valid_expression('1+cos(1)', [1, 1, 'cos', '+'], [1.5403023058681398]), + valid_expression('1563', [1563], [1563]) + ] + + for expression in valid_expressions: + calc = Calculator(expression.expression) + calc.rpn = expression.rpn + calc._calculate_rpn() + + self.assertEqual(calc.stack, expression.result_stack) + + def test_replace_unary_operator__valid_expressions(self): + """Docstring.""" + + valid_expression = namedtuple('valid_expression', 'unary_operator result') + valid_expressions = [valid_expression('-@', '-'), + valid_expression('+@', '+') + ] + + for expression in valid_expressions: + calc = Calculator('expression') + + result = calc._replace_unary_operator(expression.unary_operator) + + self.assertEqual(result, expression.result) + + def test_convert_to_number__valid_expressions(self): + """Docstring.""" + + valid_expression = namedtuple('valid_expression', 'number result') + valid_expressions = [valid_expression('569', 569), + valid_expression('789.99', 789.99), + valid_expression('-500.87', -500.87), + valid_expression([], 0) + ] + + for expression in valid_expressions: + calc = Calculator('expression') + + result = calc._convert_to_number(expression.number) + + self.assertEqual(result, expression.result) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_preprocessing.py b/tests/test_preprocessing.py new file mode 100644 index 0000000..77d9e62 --- /dev/null +++ b/tests/test_preprocessing.py @@ -0,0 +1,72 @@ +"""Unittest for module preprocessing.""" + +import unittest + +from collections import namedtuple + +from pycalc_src.preprocessing import (_preprocessing, + _is_operators_available, + _clean_repeatable_operators) +from pycalc_src.exceptions import BaseCalculatorException + +class TestStringMethods(unittest.TestCase): + """Docstring.""" + + def test_preprocessing__valid_expressions(self): + """Docstring.""" + valid_expression = namedtuple('valid_expression', 'expression result') + valid_expressions = [valid_expression('TAN(1)', 'tan(1)'), + valid_expression('**', '^') + ] + + for expression in valid_expressions: + func_result = _preprocessing(expression.expression) + + self.assertEqual(func_result, expression.result) + + def test_preprocessing__invalid_expressions(self): + """Docstring.""" + + invalid_expression = namedtuple('valid_expression', 'expression') + invalid_expressions = [invalid_expression(''), + invalid_expression(set()), + invalid_expression('(()'), + ] + + for expression in invalid_expressions: + with self.assertRaises(BaseCalculatorException): + result = _preprocessing(expression.expression) + + def test_is_operators_available__valid_expressions(self): + """Docstring.""" + valid_expression = namedtuple('valid_expression', 'expression result') + valid_expressions = [valid_expression('9-3', True), + valid_expression('pi', True), + valid_expression('43 9.8', False) + ] + + for expression in valid_expressions: + func_result = _is_operators_available(expression.expression) + + if expression.result: + self.assertTrue(func_result) + else: + self.assertFalse(func_result) + + def test_preprocessing__valid_expressions(self): + """Docstring.""" + valid_expression = namedtuple('valid_expression', 'expression result') + valid_expressions = [valid_expression('--1', '+1'), + valid_expression('-+2', '-2'), + valid_expression('++2.4', '+2.4'), + valid_expression('-+-+-++++------3', '-3') + ] + + for expression in valid_expressions: + func_result = _clean_repeatable_operators(expression.expression) + + self.assertEqual(func_result, expression.result) + + +if __name__ == '__main__': + unittest.main() From 73a738abdc49d5bcc2293afa11987d7524ce874b Mon Sep 17 00:00:00 2001 From: Roma Date: Sun, 16 Dec 2018 22:42:02 +0300 Subject: [PATCH 02/30] Update docstring for module's and function's. --- pycalc | 3 ++- pycalc_src/calculator.py | 39 ++++++++++++++++++++----------------- pycalc_src/exceptions.py | 14 ++++++------- pycalc_src/operators.py | 2 +- pycalc_src/preprocessing.py | 13 +++++-------- tests/test_preprocessing.py | 6 +++--- 6 files changed, 39 insertions(+), 38 deletions(-) diff --git a/pycalc b/pycalc index 34a8874..893c0a4 100755 --- a/pycalc +++ b/pycalc @@ -6,8 +6,9 @@ import argparse from pycalc_src import Calculator + def main(): - """Docstring.""" + """Function parse argument and calculate expression.""" parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('EXPRESSION', help='expression string to evaluate') diff --git a/pycalc_src/calculator.py b/pycalc_src/calculator.py index 9557ff5..839a85a 100644 --- a/pycalc_src/calculator.py +++ b/pycalc_src/calculator.py @@ -1,3 +1,4 @@ +"""Calculator module.""" from pycalc_src.exceptions import CalculatorError @@ -6,15 +7,15 @@ from pycalc_src.operators import UNARY_OPERATORS from pycalc_src.operators import COMPARISON_SYMBOLS -from pycalc_src.preprocessing import prepare_expression +from pycalc_src.preprocessing import preprocessing from numbers import Number + class Calculator: - """Docstring.""" + """Calculator object.""" def __init__(self, expression): - """Docstring.""" self.expression = expression self.number = '' self.operator = '' @@ -23,13 +24,13 @@ def __init__(self, expression): self.stack = [] def _process_digit(self, index, symbol): - + """Process digit from expression.""" if self.expression[index - 1] == ' ' and self.number: raise CalculatorError('invalid syntax') self.number += symbol def _process_number_and_constant(self): - + """Process number and constant.""" if self.unary_operator: self.unary_operator = self._replace_unary_operator(self.unary_operator) @@ -48,7 +49,7 @@ def _process_number_and_constant(self): self.unary_operator = '' def _process_operator(self): - + """Process operator.""" if self.unary_operator: self.stack.append(self.unary_operator) @@ -61,7 +62,7 @@ def _process_operator(self): self.operator = '' def _process_stack(self, symbol): - + """Process stack.""" while self.stack: if self.stack[-1] == symbol == '^': break @@ -74,7 +75,7 @@ def _process_stack(self, symbol): self.stack.append(symbol) def _process_comparison(self, index, symbol): - + """Process comparison.""" self._process_number_and_constant() if self.stack and self.stack[-1] in COMPARISON_SYMBOLS: @@ -88,7 +89,7 @@ def _process_comparison(self, index, symbol): self.stack.append(symbol) def _process_brackets_and_comma(self, symbol): - + """Process brackets and comma from expression.""" if symbol == ',': self._process_number_and_constant() while self.stack: @@ -113,7 +114,7 @@ def _process_brackets_and_comma(self, symbol): self.rpn.append(element) def _is_unary_operator(self, index, symbol): - + """Define that operator is unary.""" if symbol not in UNARY_OPERATORS: return False if index <= len(self.expression): @@ -124,11 +125,13 @@ def _is_unary_operator(self, index, symbol): return False def _is_floordiv(self, index, symbol): + """Define that operator is flordiv.""" if index <= len(self.expression): return symbol == self.expression[index - 1] == '/' return False def _process_expression(self): + """Process expression to reverse polish notation.""" for index, symbol in enumerate(self.expression): if self.operator in CONSTANTS: @@ -167,11 +170,11 @@ def _process_expression(self): del self.stack[:] def _calculate_operator(self, operator): - + """Prepare operator to calculate.""" operator_params = OPERATORS[operator] real_params_count = operator_params.params_quantity - if real_params_count == 3: # 'round' , 'log', 'hypot', 'atan2' + if real_params_count == 3: if self.stack and self.stack[-1] == ',': self.stack.pop() real_params_count = 2 @@ -192,7 +195,7 @@ def _calculate_operator(self, operator): self._calculate_result(operator_params.function, first_operand, second_operand) def _calculate_result(self, function, first_operand, second_operand=None): - + """Calculate function.""" try: if second_operand is None: result = function(first_operand) @@ -208,7 +211,7 @@ def _calculate_result(self, function, first_operand, second_operand=None): self.stack.append(result) def _calculate_rpn(self): - + """Calculate reverse polish notation.""" for item in self.rpn: if item == ',': self.stack.append(item) @@ -222,20 +225,20 @@ def _calculate_rpn(self): self.stack.append(item) def _replace_unary_operator(self, unary_operator): - + """Replace unary operator from raw expression.""" for key, value in UNARY_OPERATORS.items(): if value == unary_operator: return key def _convert_to_number(self, number): - """Docstring.""" + """Convert number characters to number.""" if not isinstance(number, str): return 0 return float(number) if '.' in number else int(number) def calculate(self): - """Docstring.""" - self.expression = prepare_expression(self.expression) + """Prepare and calculate expression.""" + self.expression = preprocessing(self.expression) self._process_expression() self._calculate_rpn() diff --git a/pycalc_src/exceptions.py b/pycalc_src/exceptions.py index 59f9719..f7e0c04 100644 --- a/pycalc_src/exceptions.py +++ b/pycalc_src/exceptions.py @@ -1,11 +1,11 @@ +"""Exceptions module.""" import sys class BaseCalculatorException(Exception): - """Docstring.""" + """Base calculator exception.""" def __init__(self, message=None): - """Docstring.""" if message is None: message = 'an error occured while working pycalc' @@ -13,18 +13,18 @@ def __init__(self, message=None): print(self.message) - #sys.exit(1) + sys.exit(1) class CalculatorError(BaseCalculatorException): - """Docstring.""" + """Exception for calculator.""" + def __init__(self, message=None): - """Docstring.""" super().__init__(message) class PreprocessingError(BaseCalculatorException): - """Docstring.""" + """Exception for preprocessing.""" + def __init__(self, message=None): - """Docstring.""" super().__init__(message) diff --git a/pycalc_src/operators.py b/pycalc_src/operators.py index 05eb8c0..2d1d018 100644 --- a/pycalc_src/operators.py +++ b/pycalc_src/operators.py @@ -53,7 +53,7 @@ '<=': OPERATOR(0, operator.le, 2), '==': OPERATOR(0, operator.eq, 2), '!=': OPERATOR(0, operator.ne, 2), - '>=': OPERATOR(0, operator.ge, 2), + '>=': OPERATOR(0, operator.ge, 2), '>': OPERATOR(0, operator.gt, 2), ',': OPERATOR(0, None, 0), '(': OPERATOR(0, None, 0), diff --git a/pycalc_src/preprocessing.py b/pycalc_src/preprocessing.py index 2781058..c358586 100644 --- a/pycalc_src/preprocessing.py +++ b/pycalc_src/preprocessing.py @@ -1,3 +1,4 @@ +"""Preprocessing module.""" from pycalc_src.exceptions import PreprocessingError @@ -5,8 +6,8 @@ from pycalc_src.operators import CONSTANTS -def _preprocessing(expression): - +def preprocessing(expression): + """Prepare expression for calculate.""" if not expression: raise PreprocessingError('expression is empty') @@ -29,6 +30,7 @@ def _preprocessing(expression): def _is_operators_available(expression): + """Check operators in the expression.""" for statement in OPERATORS: if statement in expression: return True @@ -40,6 +42,7 @@ def _is_operators_available(expression): def _clean_repeatable_operators(expression): + """Delete from string repeatable operators.""" repeatable_operators = {'+-': '-', '--': '+', '++': '+', '-+': '-'} while True: @@ -50,9 +53,3 @@ def _clean_repeatable_operators(expression): break return expression - - -def prepare_expression(expression): - """Docstring.""" - - return _preprocessing(expression) diff --git a/tests/test_preprocessing.py b/tests/test_preprocessing.py index 77d9e62..935e53a 100644 --- a/tests/test_preprocessing.py +++ b/tests/test_preprocessing.py @@ -4,7 +4,7 @@ from collections import namedtuple -from pycalc_src.preprocessing import (_preprocessing, +from pycalc_src.preprocessing import (preprocessing, _is_operators_available, _clean_repeatable_operators) from pycalc_src.exceptions import BaseCalculatorException @@ -20,7 +20,7 @@ def test_preprocessing__valid_expressions(self): ] for expression in valid_expressions: - func_result = _preprocessing(expression.expression) + func_result = preprocessing(expression.expression) self.assertEqual(func_result, expression.result) @@ -35,7 +35,7 @@ def test_preprocessing__invalid_expressions(self): for expression in invalid_expressions: with self.assertRaises(BaseCalculatorException): - result = _preprocessing(expression.expression) + result = preprocessing(expression.expression) def test_is_operators_available__valid_expressions(self): """Docstring.""" From d65f6185545298203c272a8cbd6bfe321bc8ff49 Mon Sep 17 00:00:00 2001 From: Roma Date: Sun, 16 Dec 2018 22:53:02 +0300 Subject: [PATCH 03/30] Pycalc moved to final_task. --- final_task/pycalc | 22 ++ final_task/pycalc_src/__init__.py | 1 + final_task/pycalc_src/calculator.py | 246 +++++++++++++++++ final_task/pycalc_src/exceptions.py | 30 +++ final_task/pycalc_src/operators.py | 65 +++++ final_task/pycalc_src/preprocessing.py | 55 ++++ final_task/setup.py | 11 + final_task/tests/__init__.py | 0 final_task/tests/test_calculator.py | 350 +++++++++++++++++++++++++ final_task/tests/test_preprocessing.py | 72 +++++ 10 files changed, 852 insertions(+) create mode 100755 final_task/pycalc create mode 100644 final_task/pycalc_src/__init__.py create mode 100644 final_task/pycalc_src/calculator.py create mode 100644 final_task/pycalc_src/exceptions.py create mode 100644 final_task/pycalc_src/operators.py create mode 100644 final_task/pycalc_src/preprocessing.py create mode 100644 final_task/tests/__init__.py create mode 100644 final_task/tests/test_calculator.py create mode 100644 final_task/tests/test_preprocessing.py diff --git a/final_task/pycalc b/final_task/pycalc new file mode 100755 index 0000000..893c0a4 --- /dev/null +++ b/final_task/pycalc @@ -0,0 +1,22 @@ +#!/usr/local/bin/python3 + +"""Pure-python command-line calculator.""" + +import argparse + +from pycalc_src import Calculator + + +def main(): + """Function parse argument and calculate expression.""" + + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('EXPRESSION', help='expression string to evaluate') + args = parser.parse_args() + + pycalc = Calculator(args.EXPRESSION) + print(pycalc.calculate()) + + +if __name__ == '__main__': + main() diff --git a/final_task/pycalc_src/__init__.py b/final_task/pycalc_src/__init__.py new file mode 100644 index 0000000..e3fcb49 --- /dev/null +++ b/final_task/pycalc_src/__init__.py @@ -0,0 +1 @@ +from pycalc_src.calculator import Calculator diff --git a/final_task/pycalc_src/calculator.py b/final_task/pycalc_src/calculator.py new file mode 100644 index 0000000..839a85a --- /dev/null +++ b/final_task/pycalc_src/calculator.py @@ -0,0 +1,246 @@ +"""Calculator module.""" + +from pycalc_src.exceptions import CalculatorError + +from pycalc_src.operators import OPERATORS +from pycalc_src.operators import CONSTANTS +from pycalc_src.operators import UNARY_OPERATORS +from pycalc_src.operators import COMPARISON_SYMBOLS + +from pycalc_src.preprocessing import preprocessing + +from numbers import Number + + +class Calculator: + """Calculator object.""" + + def __init__(self, expression): + self.expression = expression + self.number = '' + self.operator = '' + self.unary_operator = '' + self.rpn = [] + self.stack = [] + + def _process_digit(self, index, symbol): + """Process digit from expression.""" + if self.expression[index - 1] == ' ' and self.number: + raise CalculatorError('invalid syntax') + self.number += symbol + + def _process_number_and_constant(self): + """Process number and constant.""" + if self.unary_operator: + self.unary_operator = self._replace_unary_operator(self.unary_operator) + + if self.number: + self.rpn.append(self._convert_to_number('{}{}'.format(self.unary_operator, + self.number))) + self.number = '' + + if self.operator in CONSTANTS: + if self.unary_operator == '-': + self.rpn.append(0 - CONSTANTS[self.operator]) + else: + self.rpn.append(CONSTANTS[self.operator]) + self.operator = '' + + self.unary_operator = '' + + def _process_operator(self): + """Process operator.""" + if self.unary_operator: + self.stack.append(self.unary_operator) + + if self.operator: + if self.operator not in OPERATORS: + raise CalculatorError('operator not supported') + self.stack.append(self.operator) + + self.unary_operator = '' + self.operator = '' + + def _process_stack(self, symbol): + """Process stack.""" + while self.stack: + if self.stack[-1] == symbol == '^': + break + + if OPERATORS[symbol].priority <= OPERATORS[self.stack[-1]].priority: + self.rpn.append(self.stack.pop()) + else: + break + + self.stack.append(symbol) + + def _process_comparison(self, index, symbol): + """Process comparison.""" + self._process_number_and_constant() + + if self.stack and self.stack[-1] in COMPARISON_SYMBOLS: + if self.expression[index - 1] == ' ': + raise CalculatorError('unexpected whitespace') + self.stack[-1] += symbol + else: + while self.stack: + self.rpn.append(self.stack.pop()) + + self.stack.append(symbol) + + def _process_brackets_and_comma(self, symbol): + """Process brackets and comma from expression.""" + if symbol == ',': + self._process_number_and_constant() + while self.stack: + if OPERATORS[symbol].priority < OPERATORS[self.stack[-1]].priority: + self.rpn.append(self.stack.pop()) + else: + break + self.stack.append(symbol) + elif symbol == '(': + if self.number: + self._process_number_and_constant() + self.stack.append('*') + else: + self._process_operator() + self.stack.append(symbol) + elif symbol == ')': + self._process_number_and_constant() + while self.stack: + element = self.stack.pop() + if element == '(': + break + self.rpn.append(element) + + def _is_unary_operator(self, index, symbol): + """Define that operator is unary.""" + if symbol not in UNARY_OPERATORS: + return False + if index <= len(self.expression): + prev_symbol = self.expression[index - 1] + if index == 0 or (prev_symbol in OPERATORS and prev_symbol != ')' + or prev_symbol in COMPARISON_SYMBOLS): + return True + return False + + def _is_floordiv(self, index, symbol): + """Define that operator is flordiv.""" + if index <= len(self.expression): + return symbol == self.expression[index - 1] == '/' + return False + + def _process_expression(self): + """Process expression to reverse polish notation.""" + for index, symbol in enumerate(self.expression): + + if self.operator in CONSTANTS: + self._process_number_and_constant() + + if symbol in COMPARISON_SYMBOLS: + self._process_comparison(index, symbol) + continue + + if symbol.isdigit() and self.operator: + self.operator += symbol + elif symbol.isdigit() or symbol == '.': + self._process_digit(index, symbol) + elif symbol in ('(', ',', ')'): + self._process_brackets_and_comma(symbol) + elif symbol in OPERATORS: + if self.stack and self._is_floordiv(index, symbol): + self.stack[-1] += symbol + continue + + if self._is_unary_operator(index, symbol): + self.unary_operator = UNARY_OPERATORS[symbol] + continue + + self._process_number_and_constant() + self._process_stack(symbol) + elif symbol.isalpha() or symbol == '=': + self.operator += symbol + + self._process_number_and_constant() + self.rpn.extend(reversed(self.stack)) + + if not self.rpn: + raise CalculatorError('not enough data to calculate') + + del self.stack[:] + + def _calculate_operator(self, operator): + """Prepare operator to calculate.""" + operator_params = OPERATORS[operator] + + real_params_count = operator_params.params_quantity + if real_params_count == 3: + if self.stack and self.stack[-1] == ',': + self.stack.pop() + real_params_count = 2 + else: + real_params_count = 1 + + if len(self.stack) < real_params_count: + raise CalculatorError("not enough operand's for function {}".format(operator)) + elif self.stack and not isinstance(self.stack[-1], Number): + raise CalculatorError("incorrect operand's for function {}".format(operator)) + + if real_params_count == 1: + operand = self.stack.pop() + self._calculate_result(operator_params.function, operand) + elif real_params_count == 2: + second_operand = self.stack.pop() + first_operand = self.stack.pop() + self._calculate_result(operator_params.function, first_operand, second_operand) + + def _calculate_result(self, function, first_operand, second_operand=None): + """Calculate function.""" + try: + if second_operand is None: + result = function(first_operand) + else: + result = function(first_operand, second_operand) + except ZeroDivisionError as e: + raise CalculatorError(e) + except ArithmeticError as e: + raise CalculatorError(e) + except Exception as e: + raise CalculatorError(e) + else: + self.stack.append(result) + + def _calculate_rpn(self): + """Calculate reverse polish notation.""" + for item in self.rpn: + if item == ',': + self.stack.append(item) + elif item in UNARY_OPERATORS.values(): + unary_operator = self._replace_unary_operator(item) + self.stack.append(self._convert_to_number('{}1'.format(unary_operator))) + self._calculate_operator('*') + elif item in OPERATORS: + self._calculate_operator(item) + else: + self.stack.append(item) + + def _replace_unary_operator(self, unary_operator): + """Replace unary operator from raw expression.""" + for key, value in UNARY_OPERATORS.items(): + if value == unary_operator: + return key + + def _convert_to_number(self, number): + """Convert number characters to number.""" + if not isinstance(number, str): + return 0 + return float(number) if '.' in number else int(number) + + def calculate(self): + """Prepare and calculate expression.""" + self.expression = preprocessing(self.expression) + + self._process_expression() + self._calculate_rpn() + + return self.stack[-1] diff --git a/final_task/pycalc_src/exceptions.py b/final_task/pycalc_src/exceptions.py new file mode 100644 index 0000000..f7e0c04 --- /dev/null +++ b/final_task/pycalc_src/exceptions.py @@ -0,0 +1,30 @@ +"""Exceptions module.""" +import sys + + +class BaseCalculatorException(Exception): + """Base calculator exception.""" + + def __init__(self, message=None): + + if message is None: + message = 'an error occured while working pycalc' + self.message = 'ERROR: {}'.format(message) + + print(self.message) + + sys.exit(1) + + +class CalculatorError(BaseCalculatorException): + """Exception for calculator.""" + + def __init__(self, message=None): + super().__init__(message) + + +class PreprocessingError(BaseCalculatorException): + """Exception for preprocessing.""" + + def __init__(self, message=None): + super().__init__(message) diff --git a/final_task/pycalc_src/operators.py b/final_task/pycalc_src/operators.py new file mode 100644 index 0000000..2d1d018 --- /dev/null +++ b/final_task/pycalc_src/operators.py @@ -0,0 +1,65 @@ +"""Operators, constants, unary operators and comparison symbol's for pycalc.""" + +import operator +import builtins +import math + +from collections import namedtuple + + +UNARY_OPERATORS = {'-': '-@', '+': '+@'} + + +COMPARISON_SYMBOLS = ('!', '<', '>', '=') + + +OPERATOR = namedtuple('OPERATOR', 'priority function params_quantity') + + +OPERATORS = { + '+': OPERATOR(1, operator.add, 2), + '-': OPERATOR(1, operator.sub, 2), + '*': OPERATOR(2, operator.mul, 2), + '/': OPERATOR(2, operator.truediv, 2), + '//': OPERATOR(2, operator.floordiv, 2), + '%': OPERATOR(2, operator.mod, 2), + '^': OPERATOR(3, operator.pow, 2), + + 'sin': OPERATOR(4, math.sin, 1), + 'cos': OPERATOR(4, math.cos, 1), + 'asin': OPERATOR(4, math.asin, 1), + 'acos': OPERATOR(4, math.acos, 1), + 'sinh': OPERATOR(4, math.sinh, 1), + 'cosh': OPERATOR(4, math.cosh, 1), + 'asinh': OPERATOR(4, math.asinh, 1), + 'acosh': OPERATOR(4, math.acosh, 1), + 'tanh': OPERATOR(4, math.tanh, 1), + 'atanh': OPERATOR(4, math.atanh, 1), + 'tan': OPERATOR(4, math.tan, 1), + 'atan': OPERATOR(4, math.atan, 1), + 'hypot': OPERATOR(4, math.hypot, 3), + 'atan2': OPERATOR(4, math.atan2, 3), + 'exp': OPERATOR(4, math.exp, 1), + 'expm1': OPERATOR(4, math.expm1, 1), + 'log10': OPERATOR(4, math.log10, 1), + 'log2': OPERATOR(4, math.log2, 1), + 'log1p': OPERATOR(4, math.log1p, 1), + 'sqrt': OPERATOR(4, math.sqrt, 1), + 'abs': OPERATOR(4, builtins.abs, 1), + 'round': OPERATOR(4, builtins.round, 3), + 'log': OPERATOR(4, math.log, 3), + + '<': OPERATOR(0, operator.lt, 2), + '<=': OPERATOR(0, operator.le, 2), + '==': OPERATOR(0, operator.eq, 2), + '!=': OPERATOR(0, operator.ne, 2), + '>=': OPERATOR(0, operator.ge, 2), + '>': OPERATOR(0, operator.gt, 2), + ',': OPERATOR(0, None, 0), + '(': OPERATOR(0, None, 0), + ')': OPERATOR(5, None, 0), + '-@': OPERATOR(2, None, 0), + '+@': OPERATOR(2, None, 0) +} + +CONSTANTS = {a: getattr(math, a) for a in dir(math) if isinstance(getattr(math, a), float)} diff --git a/final_task/pycalc_src/preprocessing.py b/final_task/pycalc_src/preprocessing.py new file mode 100644 index 0000000..c358586 --- /dev/null +++ b/final_task/pycalc_src/preprocessing.py @@ -0,0 +1,55 @@ +"""Preprocessing module.""" + +from pycalc_src.exceptions import PreprocessingError + +from pycalc_src.operators import OPERATORS +from pycalc_src.operators import CONSTANTS + + +def preprocessing(expression): + """Prepare expression for calculate.""" + if not expression: + raise PreprocessingError('expression is empty') + + if not isinstance(expression, str): + raise PreprocessingError('expression is not a string') + + if expression.count('(') != expression.count(')'): + raise PreprocessingError('brackets are not balanced') + + expression = expression.lower() + + if not _is_operators_available(expression): + raise PreprocessingError('there are no operators in the expression') + + expression = expression.replace('**', '^') + + expression = _clean_repeatable_operators(expression) + + return expression + + +def _is_operators_available(expression): + """Check operators in the expression.""" + for statement in OPERATORS: + if statement in expression: + return True + + for statement in CONSTANTS: + if statement in expression: + return True + return False + + +def _clean_repeatable_operators(expression): + """Delete from string repeatable operators.""" + repeatable_operators = {'+-': '-', '--': '+', '++': '+', '-+': '-'} + + while True: + old_exp = expression + for old, new in repeatable_operators.items(): + expression = expression.replace(old, new) + if old_exp == expression: + break + + return expression diff --git a/final_task/setup.py b/final_task/setup.py index e69de29..b66c2a3 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", + packages=['pycalc_src'], + scripts=['pycalc'], + author="roman.yastremski", + author_email="roman.yastremski@gmail.com", + description="Pure-python command-line calculator." +) \ No newline at end of file diff --git a/final_task/tests/__init__.py b/final_task/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/final_task/tests/test_calculator.py b/final_task/tests/test_calculator.py new file mode 100644 index 0000000..f2e9ed5 --- /dev/null +++ b/final_task/tests/test_calculator.py @@ -0,0 +1,350 @@ +"""Unittest for class Calculator.""" + +import unittest + +import operator +import builtins +import math + +from collections import namedtuple + +from pycalc_src.calculator import Calculator +from pycalc_src.exceptions import BaseCalculatorException + + +class TestStringMethods(unittest.TestCase): + """Docstring.""" + + def test_process_digit__valid_expressions(self): + """Docstring.""" + valid_expression = namedtuple('valid_expression', 'expression index symbol result') + valid_expressions = [valid_expression('5', 0, '5', '5'), + valid_expression(' .', 1, '.', '.'), + valid_expression('1 ', 0, '1', '1') + ] + + for expression in valid_expressions: + calc = Calculator(expression.expression) + calc._process_digit(expression.index, expression.symbol) + + self.assertEqual(calc.number, expression.result) + + def test_process_digit__invalid_expressions(self): + """Docstring.""" + + expression = '1 2 3 4' + + calc = Calculator(expression) + calc.number = '1' + + with self.assertRaises(BaseCalculatorException): + calc._process_digit(2, '2') + + def test_process_number_and_constant__valid_expressions(self): + """Docstring.""" + + valid_expression = namedtuple('valid_expression', 'unary_operator number operator result') + valid_expressions = [valid_expression('', '54.55', '', 54.55), + valid_expression('-@', '5', '', -5), + valid_expression('', '', 'pi', math.pi), + valid_expression('-@', '', 'e', -math.e) + ] + + for expression in valid_expressions: + calc = Calculator('') + calc.unary_operator = expression.unary_operator + calc.number = expression.number + calc.operator = expression.operator + calc._process_number_and_constant() + + self.assertEqual(calc.rpn[-1], expression.result) + + def test_process_operator__valid_expressions(self): + """Docstring.""" + + valid_expression = namedtuple('valid_expression', 'unary_operator operator result') + valid_expressions = [valid_expression('', 'sin', ['sin']), + valid_expression('-@', 'log', ['-@', 'log']) + ] + + for expression in valid_expressions: + calc = Calculator('') + calc.unary_operator = expression.unary_operator + calc.operator = expression.operator + calc._process_operator() + + self.assertEqual(calc.stack, expression.result) + + def test_process_operator__invalid_expressions(self): + """Docstring.""" + + invalid_expression = namedtuple('valid_expression', 'unary_operator operator') + invalid_expressions = [invalid_expression('', 'log100'), + invalid_expression('-@', 'sin4') + ] + + for expression in invalid_expressions: + calc = Calculator('') + calc.unary_operator = expression.unary_operator + calc.operator = expression.operator + + with self.assertRaises(BaseCalculatorException): + calc._process_operator() + + def test_process_stack__valid_expressions(self): + """Docstring.""" + + valid_expression = namedtuple('valid_expression', 'stack symbol result_stack result_rpn') + valid_expressions = [valid_expression(['^'], '^', ['^', '^'], []), + valid_expression(['*'], '+', ['+'], ['*']), + valid_expression(['-'], '/', ['-', '/'], []), + valid_expression(['sin', 'tan'], '/', ['/'], ['tan', 'sin']) + ] + + for expression in valid_expressions: + calc = Calculator('') + calc.stack = expression.stack + calc._process_stack(expression.symbol) + + self.assertEqual(calc.stack, expression.result_stack) + self.assertEqual(calc.rpn, expression.result_rpn) + + def test_process_comparison__valid_expressions(self): + """Docstring.""" + + valid_expression = namedtuple('valid_expression', 'expression stack index symbol result_stack result_rpn') + valid_expressions = [valid_expression('5 >= 4', ['>'], 3, '=', ['>='], []), + valid_expression('5+1*2 > 4', ['+', '*'], 7, '>', ['>'], ['*', '+']) + ] + + for expression in valid_expressions: + calc = Calculator(expression.expression) + calc.stack = expression.stack + calc._process_comparison(expression.index, expression.symbol) + + self.assertEqual(calc.stack, expression.result_stack) + self.assertEqual(calc.rpn, expression.result_rpn) + + def test_process_comparison__invalid_expressions(self): + """Docstring.""" + + invalid_expression = namedtuple('invalid_expression', 'expression stack index symbol') + invalid_expressions = [invalid_expression('5 > = 4', ['>'], 4, '='), + invalid_expression('5+2 = = 4', ['='], 6, '=') + ] + + for expression in invalid_expressions: + calc = Calculator(expression.expression) + calc.stack = expression.stack + + with self.assertRaises(BaseCalculatorException): + calc._process_comparison(expression.index, expression.symbol) + + def test_process_brackets_and_comma__valid_expressions(self): + """Docstring.""" + + valid_expression = namedtuple('valid_expression', 'expression stack symbol number result_stack result_rpn') + valid_expressions = [ + valid_expression('round(1.22, 4)', ['round', '('], ',', '', ['round', '(', ','], []), + valid_expression('round(1.22+2, 4)', ['round', '(', '+'], ',', '', ['round', '(', ','], ['+']), + valid_expression('2 + (4)', ['+'], '(', '', ['+', '('], []), + valid_expression('2 + 2(4)', ['+'], '(', '2', ['+', '*', '('], [2]), + valid_expression('(4 + 3 * 2)', ['(', '+', '*'], ')', '', [], ['*', '+']), + valid_expression('1 + (3 * 2)', ['+', '(', '*'], ')', '', ['+'], ['*']) + ] + + for expression in valid_expressions: + calc = Calculator(expression.expression) + calc.stack = expression.stack + calc.number = expression.number + calc._process_brackets_and_comma(expression.symbol) + + self.assertEqual(calc.stack, expression.result_stack) + self.assertEqual(calc.rpn, expression.result_rpn) + + def test_is_unary_operator__valid_expressions(self): + """Docstring.""" + + valid_expression = namedtuple('valid_expression', 'expression index symbol result') + valid_expressions = [valid_expression('-4', 0, '-', True), + valid_expression('!4', 0, '!', False), + valid_expression('-4', 4, '-', False), + valid_expression('1*-4', 2, '-', True), + valid_expression('(1*2)-4', 5, '-', False), + valid_expression('5==-5', 3, '-', True) + ] + + for expression in valid_expressions: + calc = Calculator(expression.expression) + func_result = calc._is_unary_operator(expression.index, expression.symbol) + + if expression.result: + self.assertTrue(func_result) + else: + self.assertFalse(func_result) + + def test_is_floordiv__valid_expressions(self): + """Docstring.""" + + valid_expression = namedtuple('valid_expression', 'expression index symbol result') + valid_expressions = [valid_expression('5/5', 4, '', False), + valid_expression('4//3', 2, '/', True), + valid_expression('4/3', 1, '/', False) + ] + + for expression in valid_expressions: + calc = Calculator(expression.expression) + func_result = calc._is_floordiv(expression.index, expression.symbol) + + if expression.result: + self.assertTrue(func_result) + else: + self.assertFalse(func_result) + + def test_process_expression__valid_expressions(self): + """Docstring.""" + + valid_expression = namedtuple('valid_expression', 'expression result_rpn') + valid_expressions = [valid_expression('pi', [3.141592653589793]), + valid_expression('<=', ['<=']), + valid_expression('log2()', ['log2']), + valid_expression('51.567', [51.567]), + valid_expression('round(1.233333, 2)', [1.233333, 2, ',', 'round']), + valid_expression('81//8', [81, 8 , '//']), + valid_expression('//', ['//']), + valid_expression('-100', [-100]), + valid_expression('pi*log2(1)==-1', [3.141592653589793, 1, 'log2', '*', -1, '==']) + ] + + for expression in valid_expressions: + calc = Calculator(expression.expression) + calc._process_expression() + + self.assertEqual(calc.rpn, expression.result_rpn) + + def test_process_expression__invalid_expressions(self): + """Docstring.""" + + invalid_expression = namedtuple('invalid_expression', 'expression') + invalid_expressions = [invalid_expression('not an expression') + ] + + for expression in invalid_expressions: + calc = Calculator(expression.expression) + + with self.assertRaises(BaseCalculatorException): + calc._process_expression() + + def test_calculate_operator__valid_expressions(self): + """Docstring.""" + + valid_expression = namedtuple('valid_expression', 'expression stack operator result_stack') + valid_expressions = [valid_expression('1+2', [1, 2], '+', [3]), + valid_expression('round(1.2254,2)', [1.2254, 2, ','], 'round', [1.23]), + valid_expression('log(.5)', [0.5], 'log', [-0.6931471805599453]) + ] + + for expression in valid_expressions: + calc = Calculator(expression.expression) + calc.stack = expression.stack + calc._calculate_operator(expression.operator) + + self.assertEqual(calc.stack, expression.result_stack) + + def test_calculate_operator__invalid_expressions(self): + """Docstring.""" + + invalid_expression = namedtuple('invalid_expression', 'expression stack operator') + invalid_expressions = [invalid_expression('log(.5,)', [0.5, ','], 'log'), + invalid_expression('log(.5,1,2)', [0.5, 1, 2, ',', ','], 'log') + ] + + for expression in invalid_expressions: + calc = Calculator(expression.expression) + calc.stack = expression.stack + + with self.assertRaises(BaseCalculatorException): + calc._calculate_operator(expression.operator) + + def test_calculate_result__valid_expressions(self): + """Docstring.""" + + valid_expression = namedtuple('valid_expression', + 'expression function first_operand second_operand result_stack') + valid_expressions = [valid_expression('365+635', operator.add, 365, 635, [1000]), + valid_expression('sin(1)', math.sin, 1, None, [0.8414709848078965]) + ] + + for expression in valid_expressions: + calc = Calculator(expression.expression) + calc._calculate_result(expression.function, expression.first_operand, expression.second_operand) + + self.assertEqual(calc.stack, expression.result_stack) + + def test_calculate_result__invalid_expressions(self): + """Docstring.""" + + invalid_expression = namedtuple('invalid_expression', 'expression function first_operand second_operand') + invalid_expressions = [invalid_expression('5/0', operator.truediv, 5, 0), + invalid_expression('log(-100)', math.log, -100, None), + invalid_expression('log(1,,)', math.log, 1, ',') + ] + + for expression in invalid_expressions: + calc = Calculator(expression.expression) + + with self.assertRaises(BaseCalculatorException): + calc._calculate_result(expression.function, expression.first_operand, expression.second_operand) + + def test_calculate_rpn__valid_expressions(self): + """Docstring.""" + + valid_expression = namedtuple('valid_expression', 'expression rpn result_stack') + valid_expressions = [valid_expression(',', [','], [',']), + valid_expression('-(3)', [3, '-@'], [-3]), + valid_expression('1+cos(1)', [1, 1, 'cos', '+'], [1.5403023058681398]), + valid_expression('1563', [1563], [1563]) + ] + + for expression in valid_expressions: + calc = Calculator(expression.expression) + calc.rpn = expression.rpn + calc._calculate_rpn() + + self.assertEqual(calc.stack, expression.result_stack) + + def test_replace_unary_operator__valid_expressions(self): + """Docstring.""" + + valid_expression = namedtuple('valid_expression', 'unary_operator result') + valid_expressions = [valid_expression('-@', '-'), + valid_expression('+@', '+') + ] + + for expression in valid_expressions: + calc = Calculator('expression') + + result = calc._replace_unary_operator(expression.unary_operator) + + self.assertEqual(result, expression.result) + + def test_convert_to_number__valid_expressions(self): + """Docstring.""" + + valid_expression = namedtuple('valid_expression', 'number result') + valid_expressions = [valid_expression('569', 569), + valid_expression('789.99', 789.99), + valid_expression('-500.87', -500.87), + valid_expression([], 0) + ] + + for expression in valid_expressions: + calc = Calculator('expression') + + result = calc._convert_to_number(expression.number) + + self.assertEqual(result, expression.result) + + +if __name__ == '__main__': + unittest.main() diff --git a/final_task/tests/test_preprocessing.py b/final_task/tests/test_preprocessing.py new file mode 100644 index 0000000..935e53a --- /dev/null +++ b/final_task/tests/test_preprocessing.py @@ -0,0 +1,72 @@ +"""Unittest for module preprocessing.""" + +import unittest + +from collections import namedtuple + +from pycalc_src.preprocessing import (preprocessing, + _is_operators_available, + _clean_repeatable_operators) +from pycalc_src.exceptions import BaseCalculatorException + +class TestStringMethods(unittest.TestCase): + """Docstring.""" + + def test_preprocessing__valid_expressions(self): + """Docstring.""" + valid_expression = namedtuple('valid_expression', 'expression result') + valid_expressions = [valid_expression('TAN(1)', 'tan(1)'), + valid_expression('**', '^') + ] + + for expression in valid_expressions: + func_result = preprocessing(expression.expression) + + self.assertEqual(func_result, expression.result) + + def test_preprocessing__invalid_expressions(self): + """Docstring.""" + + invalid_expression = namedtuple('valid_expression', 'expression') + invalid_expressions = [invalid_expression(''), + invalid_expression(set()), + invalid_expression('(()'), + ] + + for expression in invalid_expressions: + with self.assertRaises(BaseCalculatorException): + result = preprocessing(expression.expression) + + def test_is_operators_available__valid_expressions(self): + """Docstring.""" + valid_expression = namedtuple('valid_expression', 'expression result') + valid_expressions = [valid_expression('9-3', True), + valid_expression('pi', True), + valid_expression('43 9.8', False) + ] + + for expression in valid_expressions: + func_result = _is_operators_available(expression.expression) + + if expression.result: + self.assertTrue(func_result) + else: + self.assertFalse(func_result) + + def test_preprocessing__valid_expressions(self): + """Docstring.""" + valid_expression = namedtuple('valid_expression', 'expression result') + valid_expressions = [valid_expression('--1', '+1'), + valid_expression('-+2', '-2'), + valid_expression('++2.4', '+2.4'), + valid_expression('-+-+-++++------3', '-3') + ] + + for expression in valid_expressions: + func_result = _clean_repeatable_operators(expression.expression) + + self.assertEqual(func_result, expression.result) + + +if __name__ == '__main__': + unittest.main() From dad5bd2b35cc0bc0292004b84a29a9f2c5a40eec Mon Sep 17 00:00:00 2001 From: Roma Date: Sun, 16 Dec 2018 23:05:16 +0300 Subject: [PATCH 04/30] Turn off sys.exit. --- final_task/pycalc_src/exceptions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/final_task/pycalc_src/exceptions.py b/final_task/pycalc_src/exceptions.py index f7e0c04..e667771 100644 --- a/final_task/pycalc_src/exceptions.py +++ b/final_task/pycalc_src/exceptions.py @@ -11,9 +11,9 @@ def __init__(self, message=None): message = 'an error occured while working pycalc' self.message = 'ERROR: {}'.format(message) - print(self.message) + # print(self.message) - sys.exit(1) + # sys.exit(1) class CalculatorError(BaseCalculatorException): From ed2a4b1867670bfafbac55dcd0928e636fbf5960 Mon Sep 17 00:00:00 2001 From: Roma Date: Sun, 16 Dec 2018 23:08:23 +0300 Subject: [PATCH 05/30] Add docstring for init. --- final_task/pycalc_src/exceptions.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/final_task/pycalc_src/exceptions.py b/final_task/pycalc_src/exceptions.py index e667771..17f3c31 100644 --- a/final_task/pycalc_src/exceptions.py +++ b/final_task/pycalc_src/exceptions.py @@ -6,7 +6,7 @@ class BaseCalculatorException(Exception): """Base calculator exception.""" def __init__(self, message=None): - + """"Init.""" if message is None: message = 'an error occured while working pycalc' self.message = 'ERROR: {}'.format(message) @@ -20,6 +20,7 @@ class CalculatorError(BaseCalculatorException): """Exception for calculator.""" def __init__(self, message=None): + """"Init.""" super().__init__(message) @@ -27,4 +28,5 @@ class PreprocessingError(BaseCalculatorException): """Exception for preprocessing.""" def __init__(self, message=None): + """"Init.""" super().__init__(message) From 127fdc6c5201255545d67cf840fbc58509c18e52 Mon Sep 17 00:00:00 2001 From: Roma Date: Sun, 16 Dec 2018 23:15:50 +0300 Subject: [PATCH 06/30] Add exit. --- final_task/pycalc_src/exceptions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/final_task/pycalc_src/exceptions.py b/final_task/pycalc_src/exceptions.py index 17f3c31..a69e9fa 100644 --- a/final_task/pycalc_src/exceptions.py +++ b/final_task/pycalc_src/exceptions.py @@ -11,9 +11,9 @@ def __init__(self, message=None): message = 'an error occured while working pycalc' self.message = 'ERROR: {}'.format(message) - # print(self.message) + print(self.message) - # sys.exit(1) + exit(1) class CalculatorError(BaseCalculatorException): From 83143a3cabe94149c6ae710c0621fafa3c7f40a0 Mon Sep 17 00:00:00 2001 From: Roma Date: Sun, 16 Dec 2018 23:16:45 +0300 Subject: [PATCH 07/30] In exception module add exit. --- final_task/pycalc_src/exceptions.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/final_task/pycalc_src/exceptions.py b/final_task/pycalc_src/exceptions.py index a69e9fa..f807f45 100644 --- a/final_task/pycalc_src/exceptions.py +++ b/final_task/pycalc_src/exceptions.py @@ -1,6 +1,4 @@ """Exceptions module.""" -import sys - class BaseCalculatorException(Exception): """Base calculator exception.""" From ddb1e66ab7dd7d71db5c6900b421998451cec288 Mon Sep 17 00:00:00 2001 From: Roma Date: Sun, 16 Dec 2018 23:40:01 +0300 Subject: [PATCH 08/30] Fix bugs. --- final_task/pycalc_src/calculator.py | 6 ++-- final_task/pycalc_src/exceptions.py | 1 + final_task/pycalc_src/operators.py | 1 + final_task/tests/test_calculator.py | 40 +++++++++++++------------- final_task/tests/test_preprocessing.py | 9 +++--- 5 files changed, 30 insertions(+), 27 deletions(-) diff --git a/final_task/pycalc_src/calculator.py b/final_task/pycalc_src/calculator.py index 839a85a..3ae1682 100644 --- a/final_task/pycalc_src/calculator.py +++ b/final_task/pycalc_src/calculator.py @@ -1,5 +1,7 @@ """Calculator module.""" +import numbers + from pycalc_src.exceptions import CalculatorError from pycalc_src.operators import OPERATORS @@ -9,8 +11,6 @@ from pycalc_src.preprocessing import preprocessing -from numbers import Number - class Calculator: """Calculator object.""" @@ -183,7 +183,7 @@ def _calculate_operator(self, operator): if len(self.stack) < real_params_count: raise CalculatorError("not enough operand's for function {}".format(operator)) - elif self.stack and not isinstance(self.stack[-1], Number): + elif self.stack and not isinstance(self.stack[-1], numbers.Number): raise CalculatorError("incorrect operand's for function {}".format(operator)) if real_params_count == 1: diff --git a/final_task/pycalc_src/exceptions.py b/final_task/pycalc_src/exceptions.py index f807f45..c2b8fac 100644 --- a/final_task/pycalc_src/exceptions.py +++ b/final_task/pycalc_src/exceptions.py @@ -1,5 +1,6 @@ """Exceptions module.""" + class BaseCalculatorException(Exception): """Base calculator exception.""" diff --git a/final_task/pycalc_src/operators.py b/final_task/pycalc_src/operators.py index 2d1d018..fb39fb0 100644 --- a/final_task/pycalc_src/operators.py +++ b/final_task/pycalc_src/operators.py @@ -24,6 +24,7 @@ '//': OPERATOR(2, operator.floordiv, 2), '%': OPERATOR(2, operator.mod, 2), '^': OPERATOR(3, operator.pow, 2), + 'pow': OPERATOR(3, operator.pow, 2), 'sin': OPERATOR(4, math.sin, 1), 'cos': OPERATOR(4, math.cos, 1), diff --git a/final_task/tests/test_calculator.py b/final_task/tests/test_calculator.py index f2e9ed5..2aa5705 100644 --- a/final_task/tests/test_calculator.py +++ b/final_task/tests/test_calculator.py @@ -21,7 +21,7 @@ def test_process_digit__valid_expressions(self): valid_expressions = [valid_expression('5', 0, '5', '5'), valid_expression(' .', 1, '.', '.'), valid_expression('1 ', 0, '1', '1') - ] + ] for expression in valid_expressions: calc = Calculator(expression.expression) @@ -48,7 +48,7 @@ def test_process_number_and_constant__valid_expressions(self): valid_expression('-@', '5', '', -5), valid_expression('', '', 'pi', math.pi), valid_expression('-@', '', 'e', -math.e) - ] + ] for expression in valid_expressions: calc = Calculator('') @@ -65,7 +65,7 @@ def test_process_operator__valid_expressions(self): valid_expression = namedtuple('valid_expression', 'unary_operator operator result') valid_expressions = [valid_expression('', 'sin', ['sin']), valid_expression('-@', 'log', ['-@', 'log']) - ] + ] for expression in valid_expressions: calc = Calculator('') @@ -81,7 +81,7 @@ def test_process_operator__invalid_expressions(self): invalid_expression = namedtuple('valid_expression', 'unary_operator operator') invalid_expressions = [invalid_expression('', 'log100'), invalid_expression('-@', 'sin4') - ] + ] for expression in invalid_expressions: calc = Calculator('') @@ -99,7 +99,7 @@ def test_process_stack__valid_expressions(self): valid_expression(['*'], '+', ['+'], ['*']), valid_expression(['-'], '/', ['-', '/'], []), valid_expression(['sin', 'tan'], '/', ['/'], ['tan', 'sin']) - ] + ] for expression in valid_expressions: calc = Calculator('') @@ -115,7 +115,7 @@ def test_process_comparison__valid_expressions(self): valid_expression = namedtuple('valid_expression', 'expression stack index symbol result_stack result_rpn') valid_expressions = [valid_expression('5 >= 4', ['>'], 3, '=', ['>='], []), valid_expression('5+1*2 > 4', ['+', '*'], 7, '>', ['>'], ['*', '+']) - ] + ] for expression in valid_expressions: calc = Calculator(expression.expression) @@ -131,7 +131,7 @@ def test_process_comparison__invalid_expressions(self): invalid_expression = namedtuple('invalid_expression', 'expression stack index symbol') invalid_expressions = [invalid_expression('5 > = 4', ['>'], 4, '='), invalid_expression('5+2 = = 4', ['='], 6, '=') - ] + ] for expression in invalid_expressions: calc = Calculator(expression.expression) @@ -151,7 +151,7 @@ def test_process_brackets_and_comma__valid_expressions(self): valid_expression('2 + 2(4)', ['+'], '(', '2', ['+', '*', '('], [2]), valid_expression('(4 + 3 * 2)', ['(', '+', '*'], ')', '', [], ['*', '+']), valid_expression('1 + (3 * 2)', ['+', '(', '*'], ')', '', ['+'], ['*']) - ] + ] for expression in valid_expressions: calc = Calculator(expression.expression) @@ -172,7 +172,7 @@ def test_is_unary_operator__valid_expressions(self): valid_expression('1*-4', 2, '-', True), valid_expression('(1*2)-4', 5, '-', False), valid_expression('5==-5', 3, '-', True) - ] + ] for expression in valid_expressions: calc = Calculator(expression.expression) @@ -190,7 +190,7 @@ def test_is_floordiv__valid_expressions(self): valid_expressions = [valid_expression('5/5', 4, '', False), valid_expression('4//3', 2, '/', True), valid_expression('4/3', 1, '/', False) - ] + ] for expression in valid_expressions: calc = Calculator(expression.expression) @@ -210,11 +210,11 @@ def test_process_expression__valid_expressions(self): valid_expression('log2()', ['log2']), valid_expression('51.567', [51.567]), valid_expression('round(1.233333, 2)', [1.233333, 2, ',', 'round']), - valid_expression('81//8', [81, 8 , '//']), + valid_expression('81//8', [81, 8, '//']), valid_expression('//', ['//']), valid_expression('-100', [-100]), valid_expression('pi*log2(1)==-1', [3.141592653589793, 1, 'log2', '*', -1, '==']) - ] + ] for expression in valid_expressions: calc = Calculator(expression.expression) @@ -227,7 +227,7 @@ def test_process_expression__invalid_expressions(self): invalid_expression = namedtuple('invalid_expression', 'expression') invalid_expressions = [invalid_expression('not an expression') - ] + ] for expression in invalid_expressions: calc = Calculator(expression.expression) @@ -242,7 +242,7 @@ def test_calculate_operator__valid_expressions(self): valid_expressions = [valid_expression('1+2', [1, 2], '+', [3]), valid_expression('round(1.2254,2)', [1.2254, 2, ','], 'round', [1.23]), valid_expression('log(.5)', [0.5], 'log', [-0.6931471805599453]) - ] + ] for expression in valid_expressions: calc = Calculator(expression.expression) @@ -257,7 +257,7 @@ def test_calculate_operator__invalid_expressions(self): invalid_expression = namedtuple('invalid_expression', 'expression stack operator') invalid_expressions = [invalid_expression('log(.5,)', [0.5, ','], 'log'), invalid_expression('log(.5,1,2)', [0.5, 1, 2, ',', ','], 'log') - ] + ] for expression in invalid_expressions: calc = Calculator(expression.expression) @@ -273,7 +273,7 @@ def test_calculate_result__valid_expressions(self): 'expression function first_operand second_operand result_stack') valid_expressions = [valid_expression('365+635', operator.add, 365, 635, [1000]), valid_expression('sin(1)', math.sin, 1, None, [0.8414709848078965]) - ] + ] for expression in valid_expressions: calc = Calculator(expression.expression) @@ -288,7 +288,7 @@ def test_calculate_result__invalid_expressions(self): invalid_expressions = [invalid_expression('5/0', operator.truediv, 5, 0), invalid_expression('log(-100)', math.log, -100, None), invalid_expression('log(1,,)', math.log, 1, ',') - ] + ] for expression in invalid_expressions: calc = Calculator(expression.expression) @@ -304,7 +304,7 @@ def test_calculate_rpn__valid_expressions(self): valid_expression('-(3)', [3, '-@'], [-3]), valid_expression('1+cos(1)', [1, 1, 'cos', '+'], [1.5403023058681398]), valid_expression('1563', [1563], [1563]) - ] + ] for expression in valid_expressions: calc = Calculator(expression.expression) @@ -319,7 +319,7 @@ def test_replace_unary_operator__valid_expressions(self): valid_expression = namedtuple('valid_expression', 'unary_operator result') valid_expressions = [valid_expression('-@', '-'), valid_expression('+@', '+') - ] + ] for expression in valid_expressions: calc = Calculator('expression') @@ -336,7 +336,7 @@ def test_convert_to_number__valid_expressions(self): valid_expression('789.99', 789.99), valid_expression('-500.87', -500.87), valid_expression([], 0) - ] + ] for expression in valid_expressions: calc = Calculator('expression') diff --git a/final_task/tests/test_preprocessing.py b/final_task/tests/test_preprocessing.py index 935e53a..e31f065 100644 --- a/final_task/tests/test_preprocessing.py +++ b/final_task/tests/test_preprocessing.py @@ -9,6 +9,7 @@ _clean_repeatable_operators) from pycalc_src.exceptions import BaseCalculatorException + class TestStringMethods(unittest.TestCase): """Docstring.""" @@ -17,7 +18,7 @@ def test_preprocessing__valid_expressions(self): valid_expression = namedtuple('valid_expression', 'expression result') valid_expressions = [valid_expression('TAN(1)', 'tan(1)'), valid_expression('**', '^') - ] + ] for expression in valid_expressions: func_result = preprocessing(expression.expression) @@ -31,7 +32,7 @@ def test_preprocessing__invalid_expressions(self): invalid_expressions = [invalid_expression(''), invalid_expression(set()), invalid_expression('(()'), - ] + ] for expression in invalid_expressions: with self.assertRaises(BaseCalculatorException): @@ -43,7 +44,7 @@ def test_is_operators_available__valid_expressions(self): valid_expressions = [valid_expression('9-3', True), valid_expression('pi', True), valid_expression('43 9.8', False) - ] + ] for expression in valid_expressions: func_result = _is_operators_available(expression.expression) @@ -60,7 +61,7 @@ def test_preprocessing__valid_expressions(self): valid_expression('-+2', '-2'), valid_expression('++2.4', '+2.4'), valid_expression('-+-+-++++------3', '-3') - ] + ] for expression in valid_expressions: func_result = _clean_repeatable_operators(expression.expression) From ab8d4b30fc6fbb21053ee8518590e1b4c942b385 Mon Sep 17 00:00:00 2001 From: Roma Date: Mon, 17 Dec 2018 00:26:53 +0300 Subject: [PATCH 09/30] Add new attribute __return_code to class calculator. --- final_task/pycalc_src/calculator.py | 15 ++++++++------- final_task/pycalc_src/exceptions.py | 16 ++++++++-------- final_task/pycalc_src/operators.py | 2 +- final_task/pycalc_src/preprocessing.py | 4 ++-- final_task/setup.py | 2 +- final_task/tests/test_calculator.py | 6 ++++++ 6 files changed, 26 insertions(+), 19 deletions(-) diff --git a/final_task/pycalc_src/calculator.py b/final_task/pycalc_src/calculator.py index 3ae1682..f471df1 100644 --- a/final_task/pycalc_src/calculator.py +++ b/final_task/pycalc_src/calculator.py @@ -22,11 +22,12 @@ def __init__(self, expression): self.unary_operator = '' self.rpn = [] self.stack = [] + self.__return_code = 1 def _process_digit(self, index, symbol): """Process digit from expression.""" if self.expression[index - 1] == ' ' and self.number: - raise CalculatorError('invalid syntax') + raise CalculatorError('invalid syntax', self.__return_code) self.number += symbol def _process_number_and_constant(self): @@ -55,7 +56,7 @@ def _process_operator(self): if self.operator: if self.operator not in OPERATORS: - raise CalculatorError('operator not supported') + raise CalculatorError('operator not supported', self.__return_code) self.stack.append(self.operator) self.unary_operator = '' @@ -80,7 +81,7 @@ def _process_comparison(self, index, symbol): if self.stack and self.stack[-1] in COMPARISON_SYMBOLS: if self.expression[index - 1] == ' ': - raise CalculatorError('unexpected whitespace') + raise CalculatorError('unexpected whitespace', self.__return_code) self.stack[-1] += symbol else: while self.stack: @@ -165,7 +166,7 @@ def _process_expression(self): self.rpn.extend(reversed(self.stack)) if not self.rpn: - raise CalculatorError('not enough data to calculate') + raise CalculatorError('not enough data to calculate', self.__return_code) del self.stack[:] @@ -202,11 +203,11 @@ def _calculate_result(self, function, first_operand, second_operand=None): else: result = function(first_operand, second_operand) except ZeroDivisionError as e: - raise CalculatorError(e) + raise CalculatorError(e, self.__return_code) except ArithmeticError as e: - raise CalculatorError(e) + raise CalculatorError(e, self.__return_code) except Exception as e: - raise CalculatorError(e) + raise CalculatorError(e, self.__return_code) else: self.stack.append(result) diff --git a/final_task/pycalc_src/exceptions.py b/final_task/pycalc_src/exceptions.py index c2b8fac..25ff78f 100644 --- a/final_task/pycalc_src/exceptions.py +++ b/final_task/pycalc_src/exceptions.py @@ -4,28 +4,28 @@ class BaseCalculatorException(Exception): """Base calculator exception.""" - def __init__(self, message=None): + def __init__(self, message=None, return_code=1): """"Init.""" if message is None: message = 'an error occured while working pycalc' self.message = 'ERROR: {}'.format(message) - print(self.message) - - exit(1) + if return_code == 1: + print(self.message) + exit(return_code) class CalculatorError(BaseCalculatorException): """Exception for calculator.""" - def __init__(self, message=None): + def __init__(self, message=None, return_code=1): """"Init.""" - super().__init__(message) + super().__init__(message, return_code) class PreprocessingError(BaseCalculatorException): """Exception for preprocessing.""" - def __init__(self, message=None): + def __init__(self, message=None, return_code=1): """"Init.""" - super().__init__(message) + super().__init__(message, return_code) diff --git a/final_task/pycalc_src/operators.py b/final_task/pycalc_src/operators.py index fb39fb0..f24b81b 100644 --- a/final_task/pycalc_src/operators.py +++ b/final_task/pycalc_src/operators.py @@ -24,7 +24,7 @@ '//': OPERATOR(2, operator.floordiv, 2), '%': OPERATOR(2, operator.mod, 2), '^': OPERATOR(3, operator.pow, 2), - 'pow': OPERATOR(3, operator.pow, 2), + 'pow': OPERATOR(3, operator.pow, 3), 'sin': OPERATOR(4, math.sin, 1), 'cos': OPERATOR(4, math.cos, 1), diff --git a/final_task/pycalc_src/preprocessing.py b/final_task/pycalc_src/preprocessing.py index c358586..6075570 100644 --- a/final_task/pycalc_src/preprocessing.py +++ b/final_task/pycalc_src/preprocessing.py @@ -19,8 +19,8 @@ def preprocessing(expression): expression = expression.lower() - if not _is_operators_available(expression): - raise PreprocessingError('there are no operators in the expression') + # if not _is_operators_available(expression): + # raise PreprocessingError('there are no operators in the expression') expression = expression.replace('**', '^') diff --git a/final_task/setup.py b/final_task/setup.py index b66c2a3..6f37d3f 100644 --- a/final_task/setup.py +++ b/final_task/setup.py @@ -8,4 +8,4 @@ author="roman.yastremski", author_email="roman.yastremski@gmail.com", description="Pure-python command-line calculator." -) \ No newline at end of file +) diff --git a/final_task/tests/test_calculator.py b/final_task/tests/test_calculator.py index 2aa5705..5ccc2d5 100644 --- a/final_task/tests/test_calculator.py +++ b/final_task/tests/test_calculator.py @@ -35,6 +35,7 @@ def test_process_digit__invalid_expressions(self): expression = '1 2 3 4' calc = Calculator(expression) + calc._Calculator__return_code = 0 calc.number = '1' with self.assertRaises(BaseCalculatorException): @@ -85,6 +86,7 @@ def test_process_operator__invalid_expressions(self): for expression in invalid_expressions: calc = Calculator('') + calc._Calculator__return_code = 0 calc.unary_operator = expression.unary_operator calc.operator = expression.operator @@ -135,6 +137,7 @@ def test_process_comparison__invalid_expressions(self): for expression in invalid_expressions: calc = Calculator(expression.expression) + calc._Calculator__return_code = 0 calc.stack = expression.stack with self.assertRaises(BaseCalculatorException): @@ -231,6 +234,7 @@ def test_process_expression__invalid_expressions(self): for expression in invalid_expressions: calc = Calculator(expression.expression) + calc._Calculator__return_code = 0 with self.assertRaises(BaseCalculatorException): calc._process_expression() @@ -261,6 +265,7 @@ def test_calculate_operator__invalid_expressions(self): for expression in invalid_expressions: calc = Calculator(expression.expression) + calc._Calculator__return_code = 0 calc.stack = expression.stack with self.assertRaises(BaseCalculatorException): @@ -292,6 +297,7 @@ def test_calculate_result__invalid_expressions(self): for expression in invalid_expressions: calc = Calculator(expression.expression) + calc._Calculator__return_code = 0 with self.assertRaises(BaseCalculatorException): calc._calculate_result(expression.function, expression.first_operand, expression.second_operand) From fb7f383d6055ce1feb0c99676d87c65a9add4e8a Mon Sep 17 00:00:00 2001 From: Roma Date: Mon, 17 Dec 2018 01:01:50 +0300 Subject: [PATCH 10/30] Fix test's module according to pydocstyle. --- final_task/pycalc_src/preprocessing.py | 15 ------- final_task/tests/test_calculator.py | 56 ++++++++++++++------------ final_task/tests/test_preprocessing.py | 26 +++--------- 3 files changed, 35 insertions(+), 62 deletions(-) diff --git a/final_task/pycalc_src/preprocessing.py b/final_task/pycalc_src/preprocessing.py index 6075570..2777e74 100644 --- a/final_task/pycalc_src/preprocessing.py +++ b/final_task/pycalc_src/preprocessing.py @@ -19,9 +19,6 @@ def preprocessing(expression): expression = expression.lower() - # if not _is_operators_available(expression): - # raise PreprocessingError('there are no operators in the expression') - expression = expression.replace('**', '^') expression = _clean_repeatable_operators(expression) @@ -29,18 +26,6 @@ def preprocessing(expression): return expression -def _is_operators_available(expression): - """Check operators in the expression.""" - for statement in OPERATORS: - if statement in expression: - return True - - for statement in CONSTANTS: - if statement in expression: - return True - return False - - def _clean_repeatable_operators(expression): """Delete from string repeatable operators.""" repeatable_operators = {'+-': '-', '--': '+', '++': '+', '-+': '-'} diff --git a/final_task/tests/test_calculator.py b/final_task/tests/test_calculator.py index 5ccc2d5..1725010 100644 --- a/final_task/tests/test_calculator.py +++ b/final_task/tests/test_calculator.py @@ -21,7 +21,7 @@ def test_process_digit__valid_expressions(self): valid_expressions = [valid_expression('5', 0, '5', '5'), valid_expression(' .', 1, '.', '.'), valid_expression('1 ', 0, '1', '1') - ] + ] for expression in valid_expressions: calc = Calculator(expression.expression) @@ -49,7 +49,7 @@ def test_process_number_and_constant__valid_expressions(self): valid_expression('-@', '5', '', -5), valid_expression('', '', 'pi', math.pi), valid_expression('-@', '', 'e', -math.e) - ] + ] for expression in valid_expressions: calc = Calculator('') @@ -66,7 +66,7 @@ def test_process_operator__valid_expressions(self): valid_expression = namedtuple('valid_expression', 'unary_operator operator result') valid_expressions = [valid_expression('', 'sin', ['sin']), valid_expression('-@', 'log', ['-@', 'log']) - ] + ] for expression in valid_expressions: calc = Calculator('') @@ -82,7 +82,7 @@ def test_process_operator__invalid_expressions(self): invalid_expression = namedtuple('valid_expression', 'unary_operator operator') invalid_expressions = [invalid_expression('', 'log100'), invalid_expression('-@', 'sin4') - ] + ] for expression in invalid_expressions: calc = Calculator('') @@ -101,7 +101,7 @@ def test_process_stack__valid_expressions(self): valid_expression(['*'], '+', ['+'], ['*']), valid_expression(['-'], '/', ['-', '/'], []), valid_expression(['sin', 'tan'], '/', ['/'], ['tan', 'sin']) - ] + ] for expression in valid_expressions: calc = Calculator('') @@ -117,7 +117,7 @@ def test_process_comparison__valid_expressions(self): valid_expression = namedtuple('valid_expression', 'expression stack index symbol result_stack result_rpn') valid_expressions = [valid_expression('5 >= 4', ['>'], 3, '=', ['>='], []), valid_expression('5+1*2 > 4', ['+', '*'], 7, '>', ['>'], ['*', '+']) - ] + ] for expression in valid_expressions: calc = Calculator(expression.expression) @@ -133,7 +133,7 @@ def test_process_comparison__invalid_expressions(self): invalid_expression = namedtuple('invalid_expression', 'expression stack index symbol') invalid_expressions = [invalid_expression('5 > = 4', ['>'], 4, '='), invalid_expression('5+2 = = 4', ['='], 6, '=') - ] + ] for expression in invalid_expressions: calc = Calculator(expression.expression) @@ -147,14 +147,14 @@ def test_process_brackets_and_comma__valid_expressions(self): """Docstring.""" valid_expression = namedtuple('valid_expression', 'expression stack symbol number result_stack result_rpn') - valid_expressions = [ - valid_expression('round(1.22, 4)', ['round', '('], ',', '', ['round', '(', ','], []), - valid_expression('round(1.22+2, 4)', ['round', '(', '+'], ',', '', ['round', '(', ','], ['+']), - valid_expression('2 + (4)', ['+'], '(', '', ['+', '('], []), - valid_expression('2 + 2(4)', ['+'], '(', '2', ['+', '*', '('], [2]), - valid_expression('(4 + 3 * 2)', ['(', '+', '*'], ')', '', [], ['*', '+']), - valid_expression('1 + (3 * 2)', ['+', '(', '*'], ')', '', ['+'], ['*']) - ] + valid_expressions = + [valid_expression('round(1.22, 4)', ['round', '('], ',', '', ['round', '(', ','], []), + valid_expression('round(1.22+2, 4)', ['round', '(', '+'], ',', '', ['round', '(', ','], ['+']), + valid_expression('2 + (4)', ['+'], '(', '', ['+', '('], []), + valid_expression('2 + 2(4)', ['+'], '(', '2', ['+', '*', '('], [2]), + valid_expression('(4 + 3 * 2)', ['(', '+', '*'], ')', '', [], ['*', '+']), + valid_expression('1 + (3 * 2)', ['+', '(', '*'], ')', '', ['+'], ['*']) + ] for expression in valid_expressions: calc = Calculator(expression.expression) @@ -175,7 +175,7 @@ def test_is_unary_operator__valid_expressions(self): valid_expression('1*-4', 2, '-', True), valid_expression('(1*2)-4', 5, '-', False), valid_expression('5==-5', 3, '-', True) - ] + ] for expression in valid_expressions: calc = Calculator(expression.expression) @@ -193,7 +193,7 @@ def test_is_floordiv__valid_expressions(self): valid_expressions = [valid_expression('5/5', 4, '', False), valid_expression('4//3', 2, '/', True), valid_expression('4/3', 1, '/', False) - ] + ] for expression in valid_expressions: calc = Calculator(expression.expression) @@ -217,7 +217,7 @@ def test_process_expression__valid_expressions(self): valid_expression('//', ['//']), valid_expression('-100', [-100]), valid_expression('pi*log2(1)==-1', [3.141592653589793, 1, 'log2', '*', -1, '==']) - ] + ] for expression in valid_expressions: calc = Calculator(expression.expression) @@ -230,7 +230,7 @@ def test_process_expression__invalid_expressions(self): invalid_expression = namedtuple('invalid_expression', 'expression') invalid_expressions = [invalid_expression('not an expression') - ] + ] for expression in invalid_expressions: calc = Calculator(expression.expression) @@ -246,7 +246,7 @@ def test_calculate_operator__valid_expressions(self): valid_expressions = [valid_expression('1+2', [1, 2], '+', [3]), valid_expression('round(1.2254,2)', [1.2254, 2, ','], 'round', [1.23]), valid_expression('log(.5)', [0.5], 'log', [-0.6931471805599453]) - ] + ] for expression in valid_expressions: calc = Calculator(expression.expression) @@ -261,7 +261,7 @@ def test_calculate_operator__invalid_expressions(self): invalid_expression = namedtuple('invalid_expression', 'expression stack operator') invalid_expressions = [invalid_expression('log(.5,)', [0.5, ','], 'log'), invalid_expression('log(.5,1,2)', [0.5, 1, 2, ',', ','], 'log') - ] + ] for expression in invalid_expressions: calc = Calculator(expression.expression) @@ -278,7 +278,7 @@ def test_calculate_result__valid_expressions(self): 'expression function first_operand second_operand result_stack') valid_expressions = [valid_expression('365+635', operator.add, 365, 635, [1000]), valid_expression('sin(1)', math.sin, 1, None, [0.8414709848078965]) - ] + ] for expression in valid_expressions: calc = Calculator(expression.expression) @@ -293,7 +293,7 @@ def test_calculate_result__invalid_expressions(self): invalid_expressions = [invalid_expression('5/0', operator.truediv, 5, 0), invalid_expression('log(-100)', math.log, -100, None), invalid_expression('log(1,,)', math.log, 1, ',') - ] + ] for expression in invalid_expressions: calc = Calculator(expression.expression) @@ -310,7 +310,7 @@ def test_calculate_rpn__valid_expressions(self): valid_expression('-(3)', [3, '-@'], [-3]), valid_expression('1+cos(1)', [1, 1, 'cos', '+'], [1.5403023058681398]), valid_expression('1563', [1563], [1563]) - ] + ] for expression in valid_expressions: calc = Calculator(expression.expression) @@ -325,7 +325,7 @@ def test_replace_unary_operator__valid_expressions(self): valid_expression = namedtuple('valid_expression', 'unary_operator result') valid_expressions = [valid_expression('-@', '-'), valid_expression('+@', '+') - ] + ] for expression in valid_expressions: calc = Calculator('expression') @@ -342,7 +342,7 @@ def test_convert_to_number__valid_expressions(self): valid_expression('789.99', 789.99), valid_expression('-500.87', -500.87), valid_expression([], 0) - ] + ] for expression in valid_expressions: calc = Calculator('expression') @@ -352,5 +352,9 @@ def test_convert_to_number__valid_expressions(self): self.assertEqual(result, expression.result) +class TestStringMethods(unittest.TestCase): + """Docstring.""" + + if __name__ == '__main__': unittest.main() diff --git a/final_task/tests/test_preprocessing.py b/final_task/tests/test_preprocessing.py index e31f065..4fc8371 100644 --- a/final_task/tests/test_preprocessing.py +++ b/final_task/tests/test_preprocessing.py @@ -5,8 +5,8 @@ from collections import namedtuple from pycalc_src.preprocessing import (preprocessing, - _is_operators_available, - _clean_repeatable_operators) + _is_operators_available, + _clean_repeatable_operators) from pycalc_src.exceptions import BaseCalculatorException @@ -18,7 +18,7 @@ def test_preprocessing__valid_expressions(self): valid_expression = namedtuple('valid_expression', 'expression result') valid_expressions = [valid_expression('TAN(1)', 'tan(1)'), valid_expression('**', '^') - ] + ] for expression in valid_expressions: func_result = preprocessing(expression.expression) @@ -32,28 +32,12 @@ def test_preprocessing__invalid_expressions(self): invalid_expressions = [invalid_expression(''), invalid_expression(set()), invalid_expression('(()'), - ] + ] for expression in invalid_expressions: with self.assertRaises(BaseCalculatorException): result = preprocessing(expression.expression) - def test_is_operators_available__valid_expressions(self): - """Docstring.""" - valid_expression = namedtuple('valid_expression', 'expression result') - valid_expressions = [valid_expression('9-3', True), - valid_expression('pi', True), - valid_expression('43 9.8', False) - ] - - for expression in valid_expressions: - func_result = _is_operators_available(expression.expression) - - if expression.result: - self.assertTrue(func_result) - else: - self.assertFalse(func_result) - def test_preprocessing__valid_expressions(self): """Docstring.""" valid_expression = namedtuple('valid_expression', 'expression result') @@ -61,7 +45,7 @@ def test_preprocessing__valid_expressions(self): valid_expression('-+2', '-2'), valid_expression('++2.4', '+2.4'), valid_expression('-+-+-++++------3', '-3') - ] + ] for expression in valid_expressions: func_result = _clean_repeatable_operators(expression.expression) From 228945c6ad08151df12d8496a3fc89f247347819 Mon Sep 17 00:00:00 2001 From: Roma Date: Mon, 17 Dec 2018 01:46:09 +0300 Subject: [PATCH 11/30] Create Preprocessor class. --- final_task/pycalc_src/calculator.py | 19 +++++--- final_task/pycalc_src/preprocessing.py | 40 ----------------- final_task/pycalc_src/preprocessor.py | 44 +++++++++++++++++++ final_task/tests/test_calculator.py | 20 ++++----- ..._preprocessing.py => test_preprocessor.py} | 19 ++++---- 5 files changed, 78 insertions(+), 64 deletions(-) delete mode 100644 final_task/pycalc_src/preprocessing.py create mode 100644 final_task/pycalc_src/preprocessor.py rename final_task/tests/{test_preprocessing.py => test_preprocessor.py} (72%) diff --git a/final_task/pycalc_src/calculator.py b/final_task/pycalc_src/calculator.py index f471df1..e368d1e 100644 --- a/final_task/pycalc_src/calculator.py +++ b/final_task/pycalc_src/calculator.py @@ -9,7 +9,7 @@ from pycalc_src.operators import UNARY_OPERATORS from pycalc_src.operators import COMPARISON_SYMBOLS -from pycalc_src.preprocessing import preprocessing +from pycalc_src.preprocessor import Preprocessor class Calculator: @@ -118,11 +118,17 @@ def _is_unary_operator(self, index, symbol): """Define that operator is unary.""" if symbol not in UNARY_OPERATORS: return False + if index == 0: + return True if index <= len(self.expression): - prev_symbol = self.expression[index - 1] - if index == 0 or (prev_symbol in OPERATORS and prev_symbol != ')' + for prev_symbol in reversed(self.expression[:index]): + if prev_symbol == ' ': + continue + elif (prev_symbol in OPERATORS and prev_symbol != ')' or prev_symbol in COMPARISON_SYMBOLS): - return True + return True + else: + break return False def _is_floordiv(self, index, symbol): @@ -213,6 +219,7 @@ def _calculate_result(self, function, first_operand, second_operand=None): def _calculate_rpn(self): """Calculate reverse polish notation.""" + print(self.rpn) for item in self.rpn: if item == ',': self.stack.append(item) @@ -239,8 +246,8 @@ def _convert_to_number(self, number): def calculate(self): """Prepare and calculate expression.""" - self.expression = preprocessing(self.expression) - + preprocessor = Preprocessor(self.expression) + self.expression = preprocessor.preprocessing() self._process_expression() self._calculate_rpn() diff --git a/final_task/pycalc_src/preprocessing.py b/final_task/pycalc_src/preprocessing.py deleted file mode 100644 index 2777e74..0000000 --- a/final_task/pycalc_src/preprocessing.py +++ /dev/null @@ -1,40 +0,0 @@ -"""Preprocessing module.""" - -from pycalc_src.exceptions import PreprocessingError - -from pycalc_src.operators import OPERATORS -from pycalc_src.operators import CONSTANTS - - -def preprocessing(expression): - """Prepare expression for calculate.""" - if not expression: - raise PreprocessingError('expression is empty') - - if not isinstance(expression, str): - raise PreprocessingError('expression is not a string') - - if expression.count('(') != expression.count(')'): - raise PreprocessingError('brackets are not balanced') - - expression = expression.lower() - - expression = expression.replace('**', '^') - - expression = _clean_repeatable_operators(expression) - - return expression - - -def _clean_repeatable_operators(expression): - """Delete from string repeatable operators.""" - repeatable_operators = {'+-': '-', '--': '+', '++': '+', '-+': '-'} - - while True: - old_exp = expression - for old, new in repeatable_operators.items(): - expression = expression.replace(old, new) - if old_exp == expression: - break - - return expression diff --git a/final_task/pycalc_src/preprocessor.py b/final_task/pycalc_src/preprocessor.py new file mode 100644 index 0000000..94baf50 --- /dev/null +++ b/final_task/pycalc_src/preprocessor.py @@ -0,0 +1,44 @@ +"""Preprocessing module.""" + +from pycalc_src.exceptions import PreprocessingError + +from pycalc_src.operators import OPERATORS +from pycalc_src.operators import CONSTANTS + + +class Preprocessor: + """Preprocessor object.""" + + def __init__(self, expression): + self.expression = expression + self.__return_code = 1 + + def preprocessing(self): + """Prepare expression for calculate.""" + if not self.expression: + raise PreprocessingError('expression is empty', self.__return_code) + + if not isinstance(self.expression, str): + raise PreprocessingError('expression is not a string', self.__return_code) + + if self.expression.count('(') != self.expression.count(')'): + raise PreprocessingError('brackets are not balanced', self.__return_code) + + self.expression = self.expression.lower() + + self.expression = self.expression.replace('**', '^') + + self._clean_repeatable_operators() + + return self.expression + + def _clean_repeatable_operators(self): + """Delete from string repeatable operators.""" + repeatable_operators = {'+-': '-', '--': '+', '++': '+', '-+': '-'} + + while True: + old_exp = self.expression + for old, new in repeatable_operators.items(): + self.expression = self.expression.replace(old, new) + if old_exp == self.expression: + break diff --git a/final_task/tests/test_calculator.py b/final_task/tests/test_calculator.py index 1725010..2c536c2 100644 --- a/final_task/tests/test_calculator.py +++ b/final_task/tests/test_calculator.py @@ -146,18 +146,17 @@ def test_process_comparison__invalid_expressions(self): def test_process_brackets_and_comma__valid_expressions(self): """Docstring.""" - valid_expression = namedtuple('valid_expression', 'expression stack symbol number result_stack result_rpn') - valid_expressions = - [valid_expression('round(1.22, 4)', ['round', '('], ',', '', ['round', '(', ','], []), - valid_expression('round(1.22+2, 4)', ['round', '(', '+'], ',', '', ['round', '(', ','], ['+']), - valid_expression('2 + (4)', ['+'], '(', '', ['+', '('], []), - valid_expression('2 + 2(4)', ['+'], '(', '2', ['+', '*', '('], [2]), - valid_expression('(4 + 3 * 2)', ['(', '+', '*'], ')', '', [], ['*', '+']), - valid_expression('1 + (3 * 2)', ['+', '(', '*'], ')', '', ['+'], ['*']) - ] + valid_expression = namedtuple('valid_expression', 'stack symbol number result_stack result_rpn') + valid_expressions = [valid_expression(['round', '('], ',', '', ['round', '(', ','], []), + valid_expression(['round', '(', '+'], ',', '', ['round', '(', ','], ['+']), + valid_expression(['+'], '(', '', ['+', '('], []), + valid_expression(['+'], '(', '2', ['+', '*', '('], [2]), + valid_expression(['(', '+', '*'], ')', '', [], ['*', '+']), + valid_expression(['+', '(', '*'], ')', '', ['+'], ['*']) + ] for expression in valid_expressions: - calc = Calculator(expression.expression) + calc = Calculator('expression') calc.stack = expression.stack calc.number = expression.number calc._process_brackets_and_comma(expression.symbol) @@ -170,6 +169,7 @@ def test_is_unary_operator__valid_expressions(self): valid_expression = namedtuple('valid_expression', 'expression index symbol result') valid_expressions = [valid_expression('-4', 0, '-', True), + valid_expression('- 4', 0, '-', True), valid_expression('!4', 0, '!', False), valid_expression('-4', 4, '-', False), valid_expression('1*-4', 2, '-', True), diff --git a/final_task/tests/test_preprocessing.py b/final_task/tests/test_preprocessor.py similarity index 72% rename from final_task/tests/test_preprocessing.py rename to final_task/tests/test_preprocessor.py index 4fc8371..1e8809a 100644 --- a/final_task/tests/test_preprocessing.py +++ b/final_task/tests/test_preprocessor.py @@ -4,9 +4,7 @@ from collections import namedtuple -from pycalc_src.preprocessing import (preprocessing, - _is_operators_available, - _clean_repeatable_operators) +from pycalc_src.preprocessor import Preprocessor from pycalc_src.exceptions import BaseCalculatorException @@ -21,7 +19,8 @@ def test_preprocessing__valid_expressions(self): ] for expression in valid_expressions: - func_result = preprocessing(expression.expression) + preprocessor = Preprocessor(expression.expression) + func_result = preprocessor.preprocessing() self.assertEqual(func_result, expression.result) @@ -35,10 +34,13 @@ def test_preprocessing__invalid_expressions(self): ] for expression in invalid_expressions: + preprocessor = Preprocessor(expression.expression) + preprocessor._Preprocessor__return_code = 0 + with self.assertRaises(BaseCalculatorException): - result = preprocessing(expression.expression) + result = preprocessor.preprocessing() - def test_preprocessing__valid_expressions(self): + def test_clean_repeatable_operators__valid_expressions(self): """Docstring.""" valid_expression = namedtuple('valid_expression', 'expression result') valid_expressions = [valid_expression('--1', '+1'), @@ -48,9 +50,10 @@ def test_preprocessing__valid_expressions(self): ] for expression in valid_expressions: - func_result = _clean_repeatable_operators(expression.expression) + preprocessor = Preprocessor(expression.expression) + preprocessor._clean_repeatable_operators() - self.assertEqual(func_result, expression.result) + self.assertEqual(preprocessor.expression, expression.result) if __name__ == '__main__': From 7e54cb270aaa7ba62a29139015970334216879a5 Mon Sep 17 00:00:00 2001 From: Roma Date: Mon, 17 Dec 2018 01:57:40 +0300 Subject: [PATCH 12/30] Deleted unnecessary files. --- final_task/pycalc_src/calculator.py | 2 +- pycalc | 22 -- pycalc_src/__init__.py | 1 - pycalc_src/calculator.py | 246 ------------------- pycalc_src/exceptions.py | 30 --- pycalc_src/operators.py | 65 ------ pycalc_src/preprocessing.py | 55 ----- setup.py | 11 - tests/.DS_Store | Bin 6148 -> 0 bytes tests/__init__.py | 0 tests/test_calculator.py | 350 ---------------------------- tests/test_preprocessing.py | 72 ------ 12 files changed, 1 insertion(+), 853 deletions(-) delete mode 100755 pycalc delete mode 100644 pycalc_src/__init__.py delete mode 100644 pycalc_src/calculator.py delete mode 100644 pycalc_src/exceptions.py delete mode 100644 pycalc_src/operators.py delete mode 100644 pycalc_src/preprocessing.py delete mode 100644 setup.py delete mode 100644 tests/.DS_Store delete mode 100644 tests/__init__.py delete mode 100644 tests/test_calculator.py delete mode 100644 tests/test_preprocessing.py diff --git a/final_task/pycalc_src/calculator.py b/final_task/pycalc_src/calculator.py index e368d1e..210271e 100644 --- a/final_task/pycalc_src/calculator.py +++ b/final_task/pycalc_src/calculator.py @@ -125,7 +125,7 @@ def _is_unary_operator(self, index, symbol): if prev_symbol == ' ': continue elif (prev_symbol in OPERATORS and prev_symbol != ')' - or prev_symbol in COMPARISON_SYMBOLS): + or prev_symbol in COMPARISON_SYMBOLS): return True else: break diff --git a/pycalc b/pycalc deleted file mode 100755 index 893c0a4..0000000 --- a/pycalc +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/local/bin/python3 - -"""Pure-python command-line calculator.""" - -import argparse - -from pycalc_src import Calculator - - -def main(): - """Function parse argument and calculate expression.""" - - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('EXPRESSION', help='expression string to evaluate') - args = parser.parse_args() - - pycalc = Calculator(args.EXPRESSION) - print(pycalc.calculate()) - - -if __name__ == '__main__': - main() diff --git a/pycalc_src/__init__.py b/pycalc_src/__init__.py deleted file mode 100644 index e3fcb49..0000000 --- a/pycalc_src/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from pycalc_src.calculator import Calculator diff --git a/pycalc_src/calculator.py b/pycalc_src/calculator.py deleted file mode 100644 index 839a85a..0000000 --- a/pycalc_src/calculator.py +++ /dev/null @@ -1,246 +0,0 @@ -"""Calculator module.""" - -from pycalc_src.exceptions import CalculatorError - -from pycalc_src.operators import OPERATORS -from pycalc_src.operators import CONSTANTS -from pycalc_src.operators import UNARY_OPERATORS -from pycalc_src.operators import COMPARISON_SYMBOLS - -from pycalc_src.preprocessing import preprocessing - -from numbers import Number - - -class Calculator: - """Calculator object.""" - - def __init__(self, expression): - self.expression = expression - self.number = '' - self.operator = '' - self.unary_operator = '' - self.rpn = [] - self.stack = [] - - def _process_digit(self, index, symbol): - """Process digit from expression.""" - if self.expression[index - 1] == ' ' and self.number: - raise CalculatorError('invalid syntax') - self.number += symbol - - def _process_number_and_constant(self): - """Process number and constant.""" - if self.unary_operator: - self.unary_operator = self._replace_unary_operator(self.unary_operator) - - if self.number: - self.rpn.append(self._convert_to_number('{}{}'.format(self.unary_operator, - self.number))) - self.number = '' - - if self.operator in CONSTANTS: - if self.unary_operator == '-': - self.rpn.append(0 - CONSTANTS[self.operator]) - else: - self.rpn.append(CONSTANTS[self.operator]) - self.operator = '' - - self.unary_operator = '' - - def _process_operator(self): - """Process operator.""" - if self.unary_operator: - self.stack.append(self.unary_operator) - - if self.operator: - if self.operator not in OPERATORS: - raise CalculatorError('operator not supported') - self.stack.append(self.operator) - - self.unary_operator = '' - self.operator = '' - - def _process_stack(self, symbol): - """Process stack.""" - while self.stack: - if self.stack[-1] == symbol == '^': - break - - if OPERATORS[symbol].priority <= OPERATORS[self.stack[-1]].priority: - self.rpn.append(self.stack.pop()) - else: - break - - self.stack.append(symbol) - - def _process_comparison(self, index, symbol): - """Process comparison.""" - self._process_number_and_constant() - - if self.stack and self.stack[-1] in COMPARISON_SYMBOLS: - if self.expression[index - 1] == ' ': - raise CalculatorError('unexpected whitespace') - self.stack[-1] += symbol - else: - while self.stack: - self.rpn.append(self.stack.pop()) - - self.stack.append(symbol) - - def _process_brackets_and_comma(self, symbol): - """Process brackets and comma from expression.""" - if symbol == ',': - self._process_number_and_constant() - while self.stack: - if OPERATORS[symbol].priority < OPERATORS[self.stack[-1]].priority: - self.rpn.append(self.stack.pop()) - else: - break - self.stack.append(symbol) - elif symbol == '(': - if self.number: - self._process_number_and_constant() - self.stack.append('*') - else: - self._process_operator() - self.stack.append(symbol) - elif symbol == ')': - self._process_number_and_constant() - while self.stack: - element = self.stack.pop() - if element == '(': - break - self.rpn.append(element) - - def _is_unary_operator(self, index, symbol): - """Define that operator is unary.""" - if symbol not in UNARY_OPERATORS: - return False - if index <= len(self.expression): - prev_symbol = self.expression[index - 1] - if index == 0 or (prev_symbol in OPERATORS and prev_symbol != ')' - or prev_symbol in COMPARISON_SYMBOLS): - return True - return False - - def _is_floordiv(self, index, symbol): - """Define that operator is flordiv.""" - if index <= len(self.expression): - return symbol == self.expression[index - 1] == '/' - return False - - def _process_expression(self): - """Process expression to reverse polish notation.""" - for index, symbol in enumerate(self.expression): - - if self.operator in CONSTANTS: - self._process_number_and_constant() - - if symbol in COMPARISON_SYMBOLS: - self._process_comparison(index, symbol) - continue - - if symbol.isdigit() and self.operator: - self.operator += symbol - elif symbol.isdigit() or symbol == '.': - self._process_digit(index, symbol) - elif symbol in ('(', ',', ')'): - self._process_brackets_and_comma(symbol) - elif symbol in OPERATORS: - if self.stack and self._is_floordiv(index, symbol): - self.stack[-1] += symbol - continue - - if self._is_unary_operator(index, symbol): - self.unary_operator = UNARY_OPERATORS[symbol] - continue - - self._process_number_and_constant() - self._process_stack(symbol) - elif symbol.isalpha() or symbol == '=': - self.operator += symbol - - self._process_number_and_constant() - self.rpn.extend(reversed(self.stack)) - - if not self.rpn: - raise CalculatorError('not enough data to calculate') - - del self.stack[:] - - def _calculate_operator(self, operator): - """Prepare operator to calculate.""" - operator_params = OPERATORS[operator] - - real_params_count = operator_params.params_quantity - if real_params_count == 3: - if self.stack and self.stack[-1] == ',': - self.stack.pop() - real_params_count = 2 - else: - real_params_count = 1 - - if len(self.stack) < real_params_count: - raise CalculatorError("not enough operand's for function {}".format(operator)) - elif self.stack and not isinstance(self.stack[-1], Number): - raise CalculatorError("incorrect operand's for function {}".format(operator)) - - if real_params_count == 1: - operand = self.stack.pop() - self._calculate_result(operator_params.function, operand) - elif real_params_count == 2: - second_operand = self.stack.pop() - first_operand = self.stack.pop() - self._calculate_result(operator_params.function, first_operand, second_operand) - - def _calculate_result(self, function, first_operand, second_operand=None): - """Calculate function.""" - try: - if second_operand is None: - result = function(first_operand) - else: - result = function(first_operand, second_operand) - except ZeroDivisionError as e: - raise CalculatorError(e) - except ArithmeticError as e: - raise CalculatorError(e) - except Exception as e: - raise CalculatorError(e) - else: - self.stack.append(result) - - def _calculate_rpn(self): - """Calculate reverse polish notation.""" - for item in self.rpn: - if item == ',': - self.stack.append(item) - elif item in UNARY_OPERATORS.values(): - unary_operator = self._replace_unary_operator(item) - self.stack.append(self._convert_to_number('{}1'.format(unary_operator))) - self._calculate_operator('*') - elif item in OPERATORS: - self._calculate_operator(item) - else: - self.stack.append(item) - - def _replace_unary_operator(self, unary_operator): - """Replace unary operator from raw expression.""" - for key, value in UNARY_OPERATORS.items(): - if value == unary_operator: - return key - - def _convert_to_number(self, number): - """Convert number characters to number.""" - if not isinstance(number, str): - return 0 - return float(number) if '.' in number else int(number) - - def calculate(self): - """Prepare and calculate expression.""" - self.expression = preprocessing(self.expression) - - self._process_expression() - self._calculate_rpn() - - return self.stack[-1] diff --git a/pycalc_src/exceptions.py b/pycalc_src/exceptions.py deleted file mode 100644 index f7e0c04..0000000 --- a/pycalc_src/exceptions.py +++ /dev/null @@ -1,30 +0,0 @@ -"""Exceptions module.""" -import sys - - -class BaseCalculatorException(Exception): - """Base calculator exception.""" - - def __init__(self, message=None): - - if message is None: - message = 'an error occured while working pycalc' - self.message = 'ERROR: {}'.format(message) - - print(self.message) - - sys.exit(1) - - -class CalculatorError(BaseCalculatorException): - """Exception for calculator.""" - - def __init__(self, message=None): - super().__init__(message) - - -class PreprocessingError(BaseCalculatorException): - """Exception for preprocessing.""" - - def __init__(self, message=None): - super().__init__(message) diff --git a/pycalc_src/operators.py b/pycalc_src/operators.py deleted file mode 100644 index 2d1d018..0000000 --- a/pycalc_src/operators.py +++ /dev/null @@ -1,65 +0,0 @@ -"""Operators, constants, unary operators and comparison symbol's for pycalc.""" - -import operator -import builtins -import math - -from collections import namedtuple - - -UNARY_OPERATORS = {'-': '-@', '+': '+@'} - - -COMPARISON_SYMBOLS = ('!', '<', '>', '=') - - -OPERATOR = namedtuple('OPERATOR', 'priority function params_quantity') - - -OPERATORS = { - '+': OPERATOR(1, operator.add, 2), - '-': OPERATOR(1, operator.sub, 2), - '*': OPERATOR(2, operator.mul, 2), - '/': OPERATOR(2, operator.truediv, 2), - '//': OPERATOR(2, operator.floordiv, 2), - '%': OPERATOR(2, operator.mod, 2), - '^': OPERATOR(3, operator.pow, 2), - - 'sin': OPERATOR(4, math.sin, 1), - 'cos': OPERATOR(4, math.cos, 1), - 'asin': OPERATOR(4, math.asin, 1), - 'acos': OPERATOR(4, math.acos, 1), - 'sinh': OPERATOR(4, math.sinh, 1), - 'cosh': OPERATOR(4, math.cosh, 1), - 'asinh': OPERATOR(4, math.asinh, 1), - 'acosh': OPERATOR(4, math.acosh, 1), - 'tanh': OPERATOR(4, math.tanh, 1), - 'atanh': OPERATOR(4, math.atanh, 1), - 'tan': OPERATOR(4, math.tan, 1), - 'atan': OPERATOR(4, math.atan, 1), - 'hypot': OPERATOR(4, math.hypot, 3), - 'atan2': OPERATOR(4, math.atan2, 3), - 'exp': OPERATOR(4, math.exp, 1), - 'expm1': OPERATOR(4, math.expm1, 1), - 'log10': OPERATOR(4, math.log10, 1), - 'log2': OPERATOR(4, math.log2, 1), - 'log1p': OPERATOR(4, math.log1p, 1), - 'sqrt': OPERATOR(4, math.sqrt, 1), - 'abs': OPERATOR(4, builtins.abs, 1), - 'round': OPERATOR(4, builtins.round, 3), - 'log': OPERATOR(4, math.log, 3), - - '<': OPERATOR(0, operator.lt, 2), - '<=': OPERATOR(0, operator.le, 2), - '==': OPERATOR(0, operator.eq, 2), - '!=': OPERATOR(0, operator.ne, 2), - '>=': OPERATOR(0, operator.ge, 2), - '>': OPERATOR(0, operator.gt, 2), - ',': OPERATOR(0, None, 0), - '(': OPERATOR(0, None, 0), - ')': OPERATOR(5, None, 0), - '-@': OPERATOR(2, None, 0), - '+@': OPERATOR(2, None, 0) -} - -CONSTANTS = {a: getattr(math, a) for a in dir(math) if isinstance(getattr(math, a), float)} diff --git a/pycalc_src/preprocessing.py b/pycalc_src/preprocessing.py deleted file mode 100644 index c358586..0000000 --- a/pycalc_src/preprocessing.py +++ /dev/null @@ -1,55 +0,0 @@ -"""Preprocessing module.""" - -from pycalc_src.exceptions import PreprocessingError - -from pycalc_src.operators import OPERATORS -from pycalc_src.operators import CONSTANTS - - -def preprocessing(expression): - """Prepare expression for calculate.""" - if not expression: - raise PreprocessingError('expression is empty') - - if not isinstance(expression, str): - raise PreprocessingError('expression is not a string') - - if expression.count('(') != expression.count(')'): - raise PreprocessingError('brackets are not balanced') - - expression = expression.lower() - - if not _is_operators_available(expression): - raise PreprocessingError('there are no operators in the expression') - - expression = expression.replace('**', '^') - - expression = _clean_repeatable_operators(expression) - - return expression - - -def _is_operators_available(expression): - """Check operators in the expression.""" - for statement in OPERATORS: - if statement in expression: - return True - - for statement in CONSTANTS: - if statement in expression: - return True - return False - - -def _clean_repeatable_operators(expression): - """Delete from string repeatable operators.""" - repeatable_operators = {'+-': '-', '--': '+', '++': '+', '-+': '-'} - - while True: - old_exp = expression - for old, new in repeatable_operators.items(): - expression = expression.replace(old, new) - if old_exp == expression: - break - - return expression diff --git a/setup.py b/setup.py deleted file mode 100644 index b66c2a3..0000000 --- a/setup.py +++ /dev/null @@ -1,11 +0,0 @@ -from setuptools import setup, find_packages - -setup( - name="pycalc", - version="1.0", - packages=['pycalc_src'], - scripts=['pycalc'], - author="roman.yastremski", - author_email="roman.yastremski@gmail.com", - description="Pure-python command-line calculator." -) \ No newline at end of file diff --git a/tests/.DS_Store b/tests/.DS_Store deleted file mode 100644 index 24808b6a01bcd06f6d4dd11250be39210ab7bff2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKyG{c^44j1&k!T_+$}Opo_=8oH6x4hGkU)3RA(9ky*YPd+N*F&ti4G(x3N$0x zv+J{Wb5opS0A;>=yar|frZi)`nH$5Nn~iXikwT{Nj61CHfR27Q%KkoJwddI42^;#u z`i>`U(c=YIc*g5Z*RNMyzhb`q*gfs?-NBV01*Cu!kOERb3j9_9>%GMEELdI&NC7GE zrGR}OD$Teh55fNFpz{%cJYm?uwa*gdY5{Uf9)cMmN|chLwB(3UqMY%Xc`bPej&c;g zndjuqB`1{Pw=-Tn9i;`!O93fxtiWk(XV(9hv>)dG<09>(fE4&w3Y5v>b}{EGWp5q5 xob}pDyQh7Ext>nvt(fesm>X-w7YBLO)_mTQhhQ&f+{?-OBVf7+Qs6HX_yFBk9;pBT diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/test_calculator.py b/tests/test_calculator.py deleted file mode 100644 index f2e9ed5..0000000 --- a/tests/test_calculator.py +++ /dev/null @@ -1,350 +0,0 @@ -"""Unittest for class Calculator.""" - -import unittest - -import operator -import builtins -import math - -from collections import namedtuple - -from pycalc_src.calculator import Calculator -from pycalc_src.exceptions import BaseCalculatorException - - -class TestStringMethods(unittest.TestCase): - """Docstring.""" - - def test_process_digit__valid_expressions(self): - """Docstring.""" - valid_expression = namedtuple('valid_expression', 'expression index symbol result') - valid_expressions = [valid_expression('5', 0, '5', '5'), - valid_expression(' .', 1, '.', '.'), - valid_expression('1 ', 0, '1', '1') - ] - - for expression in valid_expressions: - calc = Calculator(expression.expression) - calc._process_digit(expression.index, expression.symbol) - - self.assertEqual(calc.number, expression.result) - - def test_process_digit__invalid_expressions(self): - """Docstring.""" - - expression = '1 2 3 4' - - calc = Calculator(expression) - calc.number = '1' - - with self.assertRaises(BaseCalculatorException): - calc._process_digit(2, '2') - - def test_process_number_and_constant__valid_expressions(self): - """Docstring.""" - - valid_expression = namedtuple('valid_expression', 'unary_operator number operator result') - valid_expressions = [valid_expression('', '54.55', '', 54.55), - valid_expression('-@', '5', '', -5), - valid_expression('', '', 'pi', math.pi), - valid_expression('-@', '', 'e', -math.e) - ] - - for expression in valid_expressions: - calc = Calculator('') - calc.unary_operator = expression.unary_operator - calc.number = expression.number - calc.operator = expression.operator - calc._process_number_and_constant() - - self.assertEqual(calc.rpn[-1], expression.result) - - def test_process_operator__valid_expressions(self): - """Docstring.""" - - valid_expression = namedtuple('valid_expression', 'unary_operator operator result') - valid_expressions = [valid_expression('', 'sin', ['sin']), - valid_expression('-@', 'log', ['-@', 'log']) - ] - - for expression in valid_expressions: - calc = Calculator('') - calc.unary_operator = expression.unary_operator - calc.operator = expression.operator - calc._process_operator() - - self.assertEqual(calc.stack, expression.result) - - def test_process_operator__invalid_expressions(self): - """Docstring.""" - - invalid_expression = namedtuple('valid_expression', 'unary_operator operator') - invalid_expressions = [invalid_expression('', 'log100'), - invalid_expression('-@', 'sin4') - ] - - for expression in invalid_expressions: - calc = Calculator('') - calc.unary_operator = expression.unary_operator - calc.operator = expression.operator - - with self.assertRaises(BaseCalculatorException): - calc._process_operator() - - def test_process_stack__valid_expressions(self): - """Docstring.""" - - valid_expression = namedtuple('valid_expression', 'stack symbol result_stack result_rpn') - valid_expressions = [valid_expression(['^'], '^', ['^', '^'], []), - valid_expression(['*'], '+', ['+'], ['*']), - valid_expression(['-'], '/', ['-', '/'], []), - valid_expression(['sin', 'tan'], '/', ['/'], ['tan', 'sin']) - ] - - for expression in valid_expressions: - calc = Calculator('') - calc.stack = expression.stack - calc._process_stack(expression.symbol) - - self.assertEqual(calc.stack, expression.result_stack) - self.assertEqual(calc.rpn, expression.result_rpn) - - def test_process_comparison__valid_expressions(self): - """Docstring.""" - - valid_expression = namedtuple('valid_expression', 'expression stack index symbol result_stack result_rpn') - valid_expressions = [valid_expression('5 >= 4', ['>'], 3, '=', ['>='], []), - valid_expression('5+1*2 > 4', ['+', '*'], 7, '>', ['>'], ['*', '+']) - ] - - for expression in valid_expressions: - calc = Calculator(expression.expression) - calc.stack = expression.stack - calc._process_comparison(expression.index, expression.symbol) - - self.assertEqual(calc.stack, expression.result_stack) - self.assertEqual(calc.rpn, expression.result_rpn) - - def test_process_comparison__invalid_expressions(self): - """Docstring.""" - - invalid_expression = namedtuple('invalid_expression', 'expression stack index symbol') - invalid_expressions = [invalid_expression('5 > = 4', ['>'], 4, '='), - invalid_expression('5+2 = = 4', ['='], 6, '=') - ] - - for expression in invalid_expressions: - calc = Calculator(expression.expression) - calc.stack = expression.stack - - with self.assertRaises(BaseCalculatorException): - calc._process_comparison(expression.index, expression.symbol) - - def test_process_brackets_and_comma__valid_expressions(self): - """Docstring.""" - - valid_expression = namedtuple('valid_expression', 'expression stack symbol number result_stack result_rpn') - valid_expressions = [ - valid_expression('round(1.22, 4)', ['round', '('], ',', '', ['round', '(', ','], []), - valid_expression('round(1.22+2, 4)', ['round', '(', '+'], ',', '', ['round', '(', ','], ['+']), - valid_expression('2 + (4)', ['+'], '(', '', ['+', '('], []), - valid_expression('2 + 2(4)', ['+'], '(', '2', ['+', '*', '('], [2]), - valid_expression('(4 + 3 * 2)', ['(', '+', '*'], ')', '', [], ['*', '+']), - valid_expression('1 + (3 * 2)', ['+', '(', '*'], ')', '', ['+'], ['*']) - ] - - for expression in valid_expressions: - calc = Calculator(expression.expression) - calc.stack = expression.stack - calc.number = expression.number - calc._process_brackets_and_comma(expression.symbol) - - self.assertEqual(calc.stack, expression.result_stack) - self.assertEqual(calc.rpn, expression.result_rpn) - - def test_is_unary_operator__valid_expressions(self): - """Docstring.""" - - valid_expression = namedtuple('valid_expression', 'expression index symbol result') - valid_expressions = [valid_expression('-4', 0, '-', True), - valid_expression('!4', 0, '!', False), - valid_expression('-4', 4, '-', False), - valid_expression('1*-4', 2, '-', True), - valid_expression('(1*2)-4', 5, '-', False), - valid_expression('5==-5', 3, '-', True) - ] - - for expression in valid_expressions: - calc = Calculator(expression.expression) - func_result = calc._is_unary_operator(expression.index, expression.symbol) - - if expression.result: - self.assertTrue(func_result) - else: - self.assertFalse(func_result) - - def test_is_floordiv__valid_expressions(self): - """Docstring.""" - - valid_expression = namedtuple('valid_expression', 'expression index symbol result') - valid_expressions = [valid_expression('5/5', 4, '', False), - valid_expression('4//3', 2, '/', True), - valid_expression('4/3', 1, '/', False) - ] - - for expression in valid_expressions: - calc = Calculator(expression.expression) - func_result = calc._is_floordiv(expression.index, expression.symbol) - - if expression.result: - self.assertTrue(func_result) - else: - self.assertFalse(func_result) - - def test_process_expression__valid_expressions(self): - """Docstring.""" - - valid_expression = namedtuple('valid_expression', 'expression result_rpn') - valid_expressions = [valid_expression('pi', [3.141592653589793]), - valid_expression('<=', ['<=']), - valid_expression('log2()', ['log2']), - valid_expression('51.567', [51.567]), - valid_expression('round(1.233333, 2)', [1.233333, 2, ',', 'round']), - valid_expression('81//8', [81, 8 , '//']), - valid_expression('//', ['//']), - valid_expression('-100', [-100]), - valid_expression('pi*log2(1)==-1', [3.141592653589793, 1, 'log2', '*', -1, '==']) - ] - - for expression in valid_expressions: - calc = Calculator(expression.expression) - calc._process_expression() - - self.assertEqual(calc.rpn, expression.result_rpn) - - def test_process_expression__invalid_expressions(self): - """Docstring.""" - - invalid_expression = namedtuple('invalid_expression', 'expression') - invalid_expressions = [invalid_expression('not an expression') - ] - - for expression in invalid_expressions: - calc = Calculator(expression.expression) - - with self.assertRaises(BaseCalculatorException): - calc._process_expression() - - def test_calculate_operator__valid_expressions(self): - """Docstring.""" - - valid_expression = namedtuple('valid_expression', 'expression stack operator result_stack') - valid_expressions = [valid_expression('1+2', [1, 2], '+', [3]), - valid_expression('round(1.2254,2)', [1.2254, 2, ','], 'round', [1.23]), - valid_expression('log(.5)', [0.5], 'log', [-0.6931471805599453]) - ] - - for expression in valid_expressions: - calc = Calculator(expression.expression) - calc.stack = expression.stack - calc._calculate_operator(expression.operator) - - self.assertEqual(calc.stack, expression.result_stack) - - def test_calculate_operator__invalid_expressions(self): - """Docstring.""" - - invalid_expression = namedtuple('invalid_expression', 'expression stack operator') - invalid_expressions = [invalid_expression('log(.5,)', [0.5, ','], 'log'), - invalid_expression('log(.5,1,2)', [0.5, 1, 2, ',', ','], 'log') - ] - - for expression in invalid_expressions: - calc = Calculator(expression.expression) - calc.stack = expression.stack - - with self.assertRaises(BaseCalculatorException): - calc._calculate_operator(expression.operator) - - def test_calculate_result__valid_expressions(self): - """Docstring.""" - - valid_expression = namedtuple('valid_expression', - 'expression function first_operand second_operand result_stack') - valid_expressions = [valid_expression('365+635', operator.add, 365, 635, [1000]), - valid_expression('sin(1)', math.sin, 1, None, [0.8414709848078965]) - ] - - for expression in valid_expressions: - calc = Calculator(expression.expression) - calc._calculate_result(expression.function, expression.first_operand, expression.second_operand) - - self.assertEqual(calc.stack, expression.result_stack) - - def test_calculate_result__invalid_expressions(self): - """Docstring.""" - - invalid_expression = namedtuple('invalid_expression', 'expression function first_operand second_operand') - invalid_expressions = [invalid_expression('5/0', operator.truediv, 5, 0), - invalid_expression('log(-100)', math.log, -100, None), - invalid_expression('log(1,,)', math.log, 1, ',') - ] - - for expression in invalid_expressions: - calc = Calculator(expression.expression) - - with self.assertRaises(BaseCalculatorException): - calc._calculate_result(expression.function, expression.first_operand, expression.second_operand) - - def test_calculate_rpn__valid_expressions(self): - """Docstring.""" - - valid_expression = namedtuple('valid_expression', 'expression rpn result_stack') - valid_expressions = [valid_expression(',', [','], [',']), - valid_expression('-(3)', [3, '-@'], [-3]), - valid_expression('1+cos(1)', [1, 1, 'cos', '+'], [1.5403023058681398]), - valid_expression('1563', [1563], [1563]) - ] - - for expression in valid_expressions: - calc = Calculator(expression.expression) - calc.rpn = expression.rpn - calc._calculate_rpn() - - self.assertEqual(calc.stack, expression.result_stack) - - def test_replace_unary_operator__valid_expressions(self): - """Docstring.""" - - valid_expression = namedtuple('valid_expression', 'unary_operator result') - valid_expressions = [valid_expression('-@', '-'), - valid_expression('+@', '+') - ] - - for expression in valid_expressions: - calc = Calculator('expression') - - result = calc._replace_unary_operator(expression.unary_operator) - - self.assertEqual(result, expression.result) - - def test_convert_to_number__valid_expressions(self): - """Docstring.""" - - valid_expression = namedtuple('valid_expression', 'number result') - valid_expressions = [valid_expression('569', 569), - valid_expression('789.99', 789.99), - valid_expression('-500.87', -500.87), - valid_expression([], 0) - ] - - for expression in valid_expressions: - calc = Calculator('expression') - - result = calc._convert_to_number(expression.number) - - self.assertEqual(result, expression.result) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_preprocessing.py b/tests/test_preprocessing.py deleted file mode 100644 index 935e53a..0000000 --- a/tests/test_preprocessing.py +++ /dev/null @@ -1,72 +0,0 @@ -"""Unittest for module preprocessing.""" - -import unittest - -from collections import namedtuple - -from pycalc_src.preprocessing import (preprocessing, - _is_operators_available, - _clean_repeatable_operators) -from pycalc_src.exceptions import BaseCalculatorException - -class TestStringMethods(unittest.TestCase): - """Docstring.""" - - def test_preprocessing__valid_expressions(self): - """Docstring.""" - valid_expression = namedtuple('valid_expression', 'expression result') - valid_expressions = [valid_expression('TAN(1)', 'tan(1)'), - valid_expression('**', '^') - ] - - for expression in valid_expressions: - func_result = preprocessing(expression.expression) - - self.assertEqual(func_result, expression.result) - - def test_preprocessing__invalid_expressions(self): - """Docstring.""" - - invalid_expression = namedtuple('valid_expression', 'expression') - invalid_expressions = [invalid_expression(''), - invalid_expression(set()), - invalid_expression('(()'), - ] - - for expression in invalid_expressions: - with self.assertRaises(BaseCalculatorException): - result = preprocessing(expression.expression) - - def test_is_operators_available__valid_expressions(self): - """Docstring.""" - valid_expression = namedtuple('valid_expression', 'expression result') - valid_expressions = [valid_expression('9-3', True), - valid_expression('pi', True), - valid_expression('43 9.8', False) - ] - - for expression in valid_expressions: - func_result = _is_operators_available(expression.expression) - - if expression.result: - self.assertTrue(func_result) - else: - self.assertFalse(func_result) - - def test_preprocessing__valid_expressions(self): - """Docstring.""" - valid_expression = namedtuple('valid_expression', 'expression result') - valid_expressions = [valid_expression('--1', '+1'), - valid_expression('-+2', '-2'), - valid_expression('++2.4', '+2.4'), - valid_expression('-+-+-++++------3', '-3') - ] - - for expression in valid_expressions: - func_result = _clean_repeatable_operators(expression.expression) - - self.assertEqual(func_result, expression.result) - - -if __name__ == '__main__': - unittest.main() From 5bc9c7e34da82a501019fb81f90c1a9b1d111599 Mon Sep 17 00:00:00 2001 From: Roma Date: Mon, 17 Dec 2018 02:02:58 +0300 Subject: [PATCH 13/30] Delete unnecessary print. --- final_task/pycalc_src/calculator.py | 1 - 1 file changed, 1 deletion(-) diff --git a/final_task/pycalc_src/calculator.py b/final_task/pycalc_src/calculator.py index 210271e..dd7a195 100644 --- a/final_task/pycalc_src/calculator.py +++ b/final_task/pycalc_src/calculator.py @@ -219,7 +219,6 @@ def _calculate_result(self, function, first_operand, second_operand=None): def _calculate_rpn(self): """Calculate reverse polish notation.""" - print(self.rpn) for item in self.rpn: if item == ',': self.stack.append(item) From 00c3967f1d5543a230fc76c537c64470d3a0a26b Mon Sep 17 00:00:00 2001 From: Roma Date: Mon, 17 Dec 2018 02:08:00 +0300 Subject: [PATCH 14/30] Fixed bug's. --- final_task/tests/test_calculator.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/final_task/tests/test_calculator.py b/final_task/tests/test_calculator.py index 2c536c2..659e4bc 100644 --- a/final_task/tests/test_calculator.py +++ b/final_task/tests/test_calculator.py @@ -352,9 +352,5 @@ def test_convert_to_number__valid_expressions(self): self.assertEqual(result, expression.result) -class TestStringMethods(unittest.TestCase): - """Docstring.""" - - if __name__ == '__main__': unittest.main() From 1bb5936ad96265039fc2f37a760080b2cb47a8ca Mon Sep 17 00:00:00 2001 From: Roma Date: Mon, 17 Dec 2018 02:24:28 +0300 Subject: [PATCH 15/30] Removed import numbers. Replace to tuple. --- final_task/pycalc_src/calculator.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/final_task/pycalc_src/calculator.py b/final_task/pycalc_src/calculator.py index dd7a195..966ea84 100644 --- a/final_task/pycalc_src/calculator.py +++ b/final_task/pycalc_src/calculator.py @@ -1,7 +1,5 @@ """Calculator module.""" -import numbers - from pycalc_src.exceptions import CalculatorError from pycalc_src.operators import OPERATORS @@ -189,9 +187,9 @@ def _calculate_operator(self, operator): real_params_count = 1 if len(self.stack) < real_params_count: - raise CalculatorError("not enough operand's for function {}".format(operator)) - elif self.stack and not isinstance(self.stack[-1], numbers.Number): - raise CalculatorError("incorrect operand's for function {}".format(operator)) + raise CalculatorError("not enough operand's for function {}".format(operator), self.__return_code) + elif self.stack and not isinstance(self.stack[-1], (int, float)): + raise CalculatorError("incorrect operand's for function {}".format(operator), self.__return_code) if real_params_count == 1: operand = self.stack.pop() From e51914f6c3c5308a6f46da6fb9d15c46f5764f48 Mon Sep 17 00:00:00 2001 From: Roma Date: Mon, 17 Dec 2018 02:36:42 +0300 Subject: [PATCH 16/30] Add unittest for module exception. --- final_task/tests/test_exceptions.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 final_task/tests/test_exceptions.py diff --git a/final_task/tests/test_exceptions.py b/final_task/tests/test_exceptions.py new file mode 100644 index 0000000..fb9356e --- /dev/null +++ b/final_task/tests/test_exceptions.py @@ -0,0 +1,19 @@ +"""Unittest for module exeptions.""" + +import unittest + +from pycalc_src.exceptions import BaseCalculatorException +from pycalc_src.exceptions import CalculatorError + +class TestStringMethods(unittest.TestCase): + """Docstring.""" + + def test_base_calculator_exeption__valid_expressions(self): + """Docstring.""" + + with self.assertRaises(BaseCalculatorException): + raise CalculatorError(None, 0) + + +if __name__ == '__main__': + unittest.main() From 104cd86ab39d14d814ea035e2f0a9a43276af5c0 Mon Sep 17 00:00:00 2001 From: Roma Date: Mon, 17 Dec 2018 02:39:17 +0300 Subject: [PATCH 17/30] Add empty line in module. --- final_task/tests/test_exceptions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/final_task/tests/test_exceptions.py b/final_task/tests/test_exceptions.py index fb9356e..607000f 100644 --- a/final_task/tests/test_exceptions.py +++ b/final_task/tests/test_exceptions.py @@ -5,6 +5,7 @@ from pycalc_src.exceptions import BaseCalculatorException from pycalc_src.exceptions import CalculatorError + class TestStringMethods(unittest.TestCase): """Docstring.""" From 9e54bc3afd15cd2f243a93e91cd0979000e15a2e Mon Sep 17 00:00:00 2001 From: Roma Date: Tue, 18 Dec 2018 22:43:47 +0300 Subject: [PATCH 18/30] Add implicit multiplication. --- final_task/pycalc_src/calculator.py | 20 ++++++++++++++++---- final_task/tests/test_calculator.py | 16 ++++++++-------- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/final_task/pycalc_src/calculator.py b/final_task/pycalc_src/calculator.py index 966ea84..3cf607f 100644 --- a/final_task/pycalc_src/calculator.py +++ b/final_task/pycalc_src/calculator.py @@ -39,6 +39,10 @@ def _process_number_and_constant(self): self.number = '' if self.operator in CONSTANTS: + + if self.rpn and self.rpn[-1] in CONSTANTS.values(): + self.stack.append('*') + if self.unary_operator == '-': self.rpn.append(0 - CONSTANTS[self.operator]) else: @@ -87,7 +91,7 @@ def _process_comparison(self, index, symbol): self.stack.append(symbol) - def _process_brackets_and_comma(self, symbol): + def _process_brackets_and_comma(self, index, symbol): """Process brackets and comma from expression.""" if symbol == ',': self._process_number_and_constant() @@ -101,8 +105,15 @@ def _process_brackets_and_comma(self, symbol): if self.number: self._process_number_and_constant() self.stack.append('*') - else: - self._process_operator() + self._process_operator() + + for prev_symbol in reversed(self.expression[:index]): + if prev_symbol == ' ': + continue + if prev_symbol == ')': + self.stack.append('*') + break + self.stack.append(symbol) elif symbol == ')': self._process_number_and_constant() @@ -151,7 +162,7 @@ def _process_expression(self): elif symbol.isdigit() or symbol == '.': self._process_digit(index, symbol) elif symbol in ('(', ',', ')'): - self._process_brackets_and_comma(symbol) + self._process_brackets_and_comma(index, symbol) elif symbol in OPERATORS: if self.stack and self._is_floordiv(index, symbol): self.stack[-1] += symbol @@ -217,6 +228,7 @@ def _calculate_result(self, function, first_operand, second_operand=None): def _calculate_rpn(self): """Calculate reverse polish notation.""" + print(self.rpn) for item in self.rpn: if item == ',': self.stack.append(item) diff --git a/final_task/tests/test_calculator.py b/final_task/tests/test_calculator.py index 659e4bc..12eefd8 100644 --- a/final_task/tests/test_calculator.py +++ b/final_task/tests/test_calculator.py @@ -146,20 +146,20 @@ def test_process_comparison__invalid_expressions(self): def test_process_brackets_and_comma__valid_expressions(self): """Docstring.""" - valid_expression = namedtuple('valid_expression', 'stack symbol number result_stack result_rpn') - valid_expressions = [valid_expression(['round', '('], ',', '', ['round', '(', ','], []), - valid_expression(['round', '(', '+'], ',', '', ['round', '(', ','], ['+']), - valid_expression(['+'], '(', '', ['+', '('], []), - valid_expression(['+'], '(', '2', ['+', '*', '('], [2]), - valid_expression(['(', '+', '*'], ')', '', [], ['*', '+']), - valid_expression(['+', '(', '*'], ')', '', ['+'], ['*']) + valid_expression = namedtuple('valid_expression', 'stack index symbol number result_stack result_rpn') + valid_expressions = [valid_expression(['round', '('], 0, ',', '', ['round', '(', ','], []), + valid_expression(['round', '(', '+'], 0, ',', '', ['round', '(', ','], ['+']), + valid_expression(['+'], 0, '(', '', ['+', '('], []), + valid_expression(['+'], 0, '(', '2', ['+', '*', '('], [2]), + valid_expression(['(', '+', '*'], 0, ')', '', [], ['*', '+']), + valid_expression(['+', '(', '*'], 0, ')', '', ['+'], ['*']) ] for expression in valid_expressions: calc = Calculator('expression') calc.stack = expression.stack calc.number = expression.number - calc._process_brackets_and_comma(expression.symbol) + calc._process_brackets_and_comma(expression.index, expression.symbol) self.assertEqual(calc.stack, expression.result_stack) self.assertEqual(calc.rpn, expression.result_rpn) From 1ca5d08b4989e4970cf8c2bc60bb1a6bdf2e6ac0 Mon Sep 17 00:00:00 2001 From: Roma Date: Tue, 18 Dec 2018 22:48:40 +0300 Subject: [PATCH 19/30] Remove print. --- final_task/pycalc_src/calculator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/final_task/pycalc_src/calculator.py b/final_task/pycalc_src/calculator.py index 3cf607f..390f5f0 100644 --- a/final_task/pycalc_src/calculator.py +++ b/final_task/pycalc_src/calculator.py @@ -228,7 +228,7 @@ def _calculate_result(self, function, first_operand, second_operand=None): def _calculate_rpn(self): """Calculate reverse polish notation.""" - print(self.rpn) + #print(self.rpn) for item in self.rpn: if item == ',': self.stack.append(item) From 95d4c0c9bf6bad418e83a425f87f887c7a0f030b Mon Sep 17 00:00:00 2001 From: Roma Date: Wed, 19 Dec 2018 00:00:26 +0300 Subject: [PATCH 20/30] Improved implicit multiplication. Fix errors. --- final_task/pycalc_src/calculator.py | 20 ++++++++++++++++---- final_task/tests/test_calculator.py | 18 +++++++++--------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/final_task/pycalc_src/calculator.py b/final_task/pycalc_src/calculator.py index 390f5f0..bed0189 100644 --- a/final_task/pycalc_src/calculator.py +++ b/final_task/pycalc_src/calculator.py @@ -40,7 +40,7 @@ def _process_number_and_constant(self): if self.operator in CONSTANTS: - if self.rpn and self.rpn[-1] in CONSTANTS.values(): + if self.rpn and self.rpn[-1] in CONSTANTS.values() and not self.stack: self.stack.append('*') if self.unary_operator == '-': @@ -51,7 +51,7 @@ def _process_number_and_constant(self): self.unary_operator = '' - def _process_operator(self): + def _process_operator(self, cloing_bracket_index): """Process operator.""" if self.unary_operator: self.stack.append(self.unary_operator) @@ -59,6 +59,15 @@ def _process_operator(self): if self.operator: if self.operator not in OPERATORS: raise CalculatorError('operator not supported', self.__return_code) + + for prev_symbol in reversed(self.expression[:cloing_bracket_index - len(self.operator)]): + if prev_symbol == ' ': + continue + elif prev_symbol == ')': + self.stack.append('*') + break + break + self.stack.append(self.operator) self.unary_operator = '' @@ -105,7 +114,7 @@ def _process_brackets_and_comma(self, index, symbol): if self.number: self._process_number_and_constant() self.stack.append('*') - self._process_operator() + self._process_operator(index) for prev_symbol in reversed(self.expression[:index]): if prev_symbol == ' ': @@ -123,6 +132,10 @@ def _process_brackets_and_comma(self, index, symbol): break self.rpn.append(element) + if self.stack: + element = self.stack.pop() + self.rpn.append(element) + def _is_unary_operator(self, index, symbol): """Define that operator is unary.""" if symbol not in UNARY_OPERATORS: @@ -228,7 +241,6 @@ def _calculate_result(self, function, first_operand, second_operand=None): def _calculate_rpn(self): """Calculate reverse polish notation.""" - #print(self.rpn) for item in self.rpn: if item == ',': self.stack.append(item) diff --git a/final_task/tests/test_calculator.py b/final_task/tests/test_calculator.py index 12eefd8..1445939 100644 --- a/final_task/tests/test_calculator.py +++ b/final_task/tests/test_calculator.py @@ -63,25 +63,25 @@ def test_process_number_and_constant__valid_expressions(self): def test_process_operator__valid_expressions(self): """Docstring.""" - valid_expression = namedtuple('valid_expression', 'unary_operator operator result') - valid_expressions = [valid_expression('', 'sin', ['sin']), - valid_expression('-@', 'log', ['-@', 'log']) + valid_expression = namedtuple('valid_expression', 'unary_operator operator closing_bracket_index result') + valid_expressions = [valid_expression('', 'sin', 2, ['sin']), + valid_expression('-@', 'log', 2, ['-@', 'log']) ] for expression in valid_expressions: calc = Calculator('') calc.unary_operator = expression.unary_operator calc.operator = expression.operator - calc._process_operator() + calc._process_operator(expression.closing_bracket_index) self.assertEqual(calc.stack, expression.result) def test_process_operator__invalid_expressions(self): """Docstring.""" - invalid_expression = namedtuple('valid_expression', 'unary_operator operator') - invalid_expressions = [invalid_expression('', 'log100'), - invalid_expression('-@', 'sin4') + invalid_expression = namedtuple('valid_expression', 'unary_operator operator closing_bracket_index') + invalid_expressions = [invalid_expression('', 'log100', 0), + invalid_expression('-@', 'sin4', 0) ] for expression in invalid_expressions: @@ -91,7 +91,7 @@ def test_process_operator__invalid_expressions(self): calc.operator = expression.operator with self.assertRaises(BaseCalculatorException): - calc._process_operator() + calc._process_operator(expression.closing_bracket_index) def test_process_stack__valid_expressions(self): """Docstring.""" @@ -152,7 +152,7 @@ def test_process_brackets_and_comma__valid_expressions(self): valid_expression(['+'], 0, '(', '', ['+', '('], []), valid_expression(['+'], 0, '(', '2', ['+', '*', '('], [2]), valid_expression(['(', '+', '*'], 0, ')', '', [], ['*', '+']), - valid_expression(['+', '(', '*'], 0, ')', '', ['+'], ['*']) + valid_expression(['+', '(', '*'], 0, ')', '', [], ['*', '+']) ] for expression in valid_expressions: From 6a6beeb1ec0697c1aa38be7320f685ab7e24c03c Mon Sep 17 00:00:00 2001 From: Roma Date: Wed, 19 Dec 2018 00:30:31 +0300 Subject: [PATCH 21/30] Improved implicit multiplication. --- final_task/pycalc_src/calculator.py | 5 +- final_task/pycalc_src/operators.py | 86 ++++++++++++++--------------- 2 files changed, 45 insertions(+), 46 deletions(-) diff --git a/final_task/pycalc_src/calculator.py b/final_task/pycalc_src/calculator.py index bed0189..73df487 100644 --- a/final_task/pycalc_src/calculator.py +++ b/final_task/pycalc_src/calculator.py @@ -132,9 +132,8 @@ def _process_brackets_and_comma(self, index, symbol): break self.rpn.append(element) - if self.stack: - element = self.stack.pop() - self.rpn.append(element) + if self.stack and OPERATORS[self.stack[-1]].have_brackets: + self.rpn.append(self.stack.pop()) def _is_unary_operator(self, index, symbol): """Define that operator is unary.""" diff --git a/final_task/pycalc_src/operators.py b/final_task/pycalc_src/operators.py index f24b81b..ffc66d6 100644 --- a/final_task/pycalc_src/operators.py +++ b/final_task/pycalc_src/operators.py @@ -13,54 +13,54 @@ COMPARISON_SYMBOLS = ('!', '<', '>', '=') -OPERATOR = namedtuple('OPERATOR', 'priority function params_quantity') +OPERATOR = namedtuple('OPERATOR', 'priority function params_quantity have_brackets') OPERATORS = { - '+': OPERATOR(1, operator.add, 2), - '-': OPERATOR(1, operator.sub, 2), - '*': OPERATOR(2, operator.mul, 2), - '/': OPERATOR(2, operator.truediv, 2), - '//': OPERATOR(2, operator.floordiv, 2), - '%': OPERATOR(2, operator.mod, 2), - '^': OPERATOR(3, operator.pow, 2), - 'pow': OPERATOR(3, operator.pow, 3), + '+': OPERATOR(1, operator.add, 2, False), + '-': OPERATOR(1, operator.sub, 2, False), + '*': OPERATOR(2, operator.mul, 2, False), + '/': OPERATOR(2, operator.truediv, 2, False), + '//': OPERATOR(2, operator.floordiv, 2, False), + '%': OPERATOR(2, operator.mod, 2, False), + '^': OPERATOR(3, operator.pow, 2, False), + 'pow': OPERATOR(3, operator.pow, 3, True), - 'sin': OPERATOR(4, math.sin, 1), - 'cos': OPERATOR(4, math.cos, 1), - 'asin': OPERATOR(4, math.asin, 1), - 'acos': OPERATOR(4, math.acos, 1), - 'sinh': OPERATOR(4, math.sinh, 1), - 'cosh': OPERATOR(4, math.cosh, 1), - 'asinh': OPERATOR(4, math.asinh, 1), - 'acosh': OPERATOR(4, math.acosh, 1), - 'tanh': OPERATOR(4, math.tanh, 1), - 'atanh': OPERATOR(4, math.atanh, 1), - 'tan': OPERATOR(4, math.tan, 1), - 'atan': OPERATOR(4, math.atan, 1), - 'hypot': OPERATOR(4, math.hypot, 3), - 'atan2': OPERATOR(4, math.atan2, 3), - 'exp': OPERATOR(4, math.exp, 1), - 'expm1': OPERATOR(4, math.expm1, 1), - 'log10': OPERATOR(4, math.log10, 1), - 'log2': OPERATOR(4, math.log2, 1), - 'log1p': OPERATOR(4, math.log1p, 1), - 'sqrt': OPERATOR(4, math.sqrt, 1), - 'abs': OPERATOR(4, builtins.abs, 1), - 'round': OPERATOR(4, builtins.round, 3), - 'log': OPERATOR(4, math.log, 3), + 'sin': OPERATOR(4, math.sin, 1, True), + 'cos': OPERATOR(4, math.cos, 1, True), + 'asin': OPERATOR(4, math.asin, 1, True), + 'acos': OPERATOR(4, math.acos, 1, True), + 'sinh': OPERATOR(4, math.sinh, 1, True), + 'cosh': OPERATOR(4, math.cosh, 1, True), + 'asinh': OPERATOR(4, math.asinh, 1, True), + 'acosh': OPERATOR(4, math.acosh, 1, True), + 'tanh': OPERATOR(4, math.tanh, 1, True), + 'atanh': OPERATOR(4, math.atanh, 1, True), + 'tan': OPERATOR(4, math.tan, 1, True), + 'atan': OPERATOR(4, math.atan, 1, True), + 'hypot': OPERATOR(4, math.hypot, 3, True), + 'atan2': OPERATOR(4, math.atan2, 3, True), + 'exp': OPERATOR(4, math.exp, 1, True), + 'expm1': OPERATOR(4, math.expm1, 1, True), + 'log10': OPERATOR(4, math.log10, 1, True), + 'log2': OPERATOR(4, math.log2, 1, True), + 'log1p': OPERATOR(4, math.log1p, 1, True), + 'sqrt': OPERATOR(4, math.sqrt, 1, True), + 'abs': OPERATOR(4, builtins.abs, 1, True), + 'round': OPERATOR(4, builtins.round, 3, True), + 'log': OPERATOR(4, math.log, 3, True), - '<': OPERATOR(0, operator.lt, 2), - '<=': OPERATOR(0, operator.le, 2), - '==': OPERATOR(0, operator.eq, 2), - '!=': OPERATOR(0, operator.ne, 2), - '>=': OPERATOR(0, operator.ge, 2), - '>': OPERATOR(0, operator.gt, 2), - ',': OPERATOR(0, None, 0), - '(': OPERATOR(0, None, 0), - ')': OPERATOR(5, None, 0), - '-@': OPERATOR(2, None, 0), - '+@': OPERATOR(2, None, 0) + '<': OPERATOR(0, operator.lt, 2, False), + '<=': OPERATOR(0, operator.le, 2, False), + '==': OPERATOR(0, operator.eq, 2, False), + '!=': OPERATOR(0, operator.ne, 2, False), + '>=': OPERATOR(0, operator.ge, 2, False), + '>': OPERATOR(0, operator.gt, 2, False), + ',': OPERATOR(0, None, 0, False), + '(': OPERATOR(0, None, 0, False), + ')': OPERATOR(5, None, 0, False), + '-@': OPERATOR(2, None, 0, False), + '+@': OPERATOR(2, None, 0, False) } CONSTANTS = {a: getattr(math, a) for a in dir(math) if isinstance(getattr(math, a), float)} From 3b8659a6ad8dbf882b53602122366d600a1f4d4a Mon Sep 17 00:00:00 2001 From: Roma Date: Wed, 19 Dec 2018 00:34:22 +0300 Subject: [PATCH 22/30] Fix unittest for function _process_brackets_and_comma. --- final_task/tests/test_calculator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/final_task/tests/test_calculator.py b/final_task/tests/test_calculator.py index 1445939..5ae9cd6 100644 --- a/final_task/tests/test_calculator.py +++ b/final_task/tests/test_calculator.py @@ -152,7 +152,7 @@ def test_process_brackets_and_comma__valid_expressions(self): valid_expression(['+'], 0, '(', '', ['+', '('], []), valid_expression(['+'], 0, '(', '2', ['+', '*', '('], [2]), valid_expression(['(', '+', '*'], 0, ')', '', [], ['*', '+']), - valid_expression(['+', '(', '*'], 0, ')', '', [], ['*', '+']) + valid_expression(['+', '(', '*'], 0, ')', '', ['+'], ['*']) ] for expression in valid_expressions: From 05a26f0fea24058c312ce5f3812dbb22c1a30e3a Mon Sep 17 00:00:00 2001 From: Roma Date: Wed, 19 Dec 2018 01:23:57 +0300 Subject: [PATCH 23/30] Add function's and unittest's. - _get_previous_symbol - _process_implicit_multiplication --- final_task/pycalc_src/calculator.py | 30 +++++++++++++++-------------- final_task/tests/test_calculator.py | 28 +++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 14 deletions(-) diff --git a/final_task/pycalc_src/calculator.py b/final_task/pycalc_src/calculator.py index 73df487..7fb0ab3 100644 --- a/final_task/pycalc_src/calculator.py +++ b/final_task/pycalc_src/calculator.py @@ -51,7 +51,7 @@ def _process_number_and_constant(self): self.unary_operator = '' - def _process_operator(self, cloing_bracket_index): + def _process_operator(self, closing_bracket_index): """Process operator.""" if self.unary_operator: self.stack.append(self.unary_operator) @@ -60,13 +60,7 @@ def _process_operator(self, cloing_bracket_index): if self.operator not in OPERATORS: raise CalculatorError('operator not supported', self.__return_code) - for prev_symbol in reversed(self.expression[:cloing_bracket_index - len(self.operator)]): - if prev_symbol == ' ': - continue - elif prev_symbol == ')': - self.stack.append('*') - break - break + self._process_implicit_multiplication(closing_bracket_index - len(self.operator)) self.stack.append(self.operator) @@ -116,12 +110,7 @@ def _process_brackets_and_comma(self, index, symbol): self.stack.append('*') self._process_operator(index) - for prev_symbol in reversed(self.expression[:index]): - if prev_symbol == ' ': - continue - if prev_symbol == ')': - self.stack.append('*') - break + self._process_implicit_multiplication(index) self.stack.append(symbol) elif symbol == ')': @@ -197,6 +186,12 @@ def _process_expression(self): del self.stack[:] + def _process_implicit_multiplication(self, index): + """Сhecks for implicit multiplication.""" + prev_symbol = self._get_previous_symbol(index) + if prev_symbol == ')': + self.stack.append('*') + def _calculate_operator(self, operator): """Prepare operator to calculate.""" operator_params = OPERATORS[operator] @@ -264,6 +259,13 @@ def _convert_to_number(self, number): return 0 return float(number) if '.' in number else int(number) + def _get_previous_symbol(self, index): + """Return previous symbol excluding whitespace.""" + for prev_symbol in reversed(self.expression[:index]): + if prev_symbol == ' ': + continue + return prev_symbol + def calculate(self): """Prepare and calculate expression.""" preprocessor = Preprocessor(self.expression) diff --git a/final_task/tests/test_calculator.py b/final_task/tests/test_calculator.py index 5ae9cd6..e1fbf9d 100644 --- a/final_task/tests/test_calculator.py +++ b/final_task/tests/test_calculator.py @@ -351,6 +351,34 @@ def test_convert_to_number__valid_expressions(self): self.assertEqual(result, expression.result) + def test_process_implicit_multiplication__valid_expressions(self): + """Docstring.""" + + valid_expression = namedtuple('valid_expression', 'expression index result_stack') + valid_expressions = [valid_expression('(1)(1+2)', 3, ['*']) + ] + + for expression in valid_expressions: + calc = Calculator(expression.expression) + + calc._process_implicit_multiplication(expression.index) + + self.assertEqual(calc.stack, expression.result_stack) + + def test_get_previous_symbol__valid_expressions(self): + """Docstring.""" + + valid_expression = namedtuple('valid_expression', 'expression index result') + valid_expressions = [valid_expression('1 + 2', 5, '+') + ] + + for expression in valid_expressions: + calc = Calculator(expression.expression) + + result = calc._get_previous_symbol(expression.index) + + self.assertEqual(result, expression.result) + if __name__ == '__main__': unittest.main() From abccd7332cd09da6d4566f4329eb34ca0335087d Mon Sep 17 00:00:00 2001 From: Roma Date: Wed, 19 Dec 2018 01:38:55 +0300 Subject: [PATCH 24/30] Code refactoring in function _is_unary_operator. --- final_task/pycalc_src/calculator.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/final_task/pycalc_src/calculator.py b/final_task/pycalc_src/calculator.py index 7fb0ab3..0eacd1d 100644 --- a/final_task/pycalc_src/calculator.py +++ b/final_task/pycalc_src/calculator.py @@ -131,14 +131,10 @@ def _is_unary_operator(self, index, symbol): if index == 0: return True if index <= len(self.expression): - for prev_symbol in reversed(self.expression[:index]): - if prev_symbol == ' ': - continue - elif (prev_symbol in OPERATORS and prev_symbol != ')' - or prev_symbol in COMPARISON_SYMBOLS): + prev_symbol = self._get_previous_symbol(index) + if (prev_symbol in OPERATORS and prev_symbol != ')' + or prev_symbol in COMPARISON_SYMBOLS): return True - else: - break return False def _is_floordiv(self, index, symbol): @@ -260,7 +256,7 @@ def _convert_to_number(self, number): return float(number) if '.' in number else int(number) def _get_previous_symbol(self, index): - """Return previous symbol excluding whitespace.""" + """Return previous symbol excluding whitespace's.""" for prev_symbol in reversed(self.expression[:index]): if prev_symbol == ' ': continue From 1450d85330bee3c7115ee51e5e4d816f419ccb53 Mon Sep 17 00:00:00 2001 From: Roma Date: Wed, 19 Dec 2018 01:43:06 +0300 Subject: [PATCH 25/30] Fix if statement in function _is_unary_operator. --- final_task/pycalc_src/calculator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/final_task/pycalc_src/calculator.py b/final_task/pycalc_src/calculator.py index 0eacd1d..cdd96cc 100644 --- a/final_task/pycalc_src/calculator.py +++ b/final_task/pycalc_src/calculator.py @@ -133,7 +133,7 @@ def _is_unary_operator(self, index, symbol): if index <= len(self.expression): prev_symbol = self._get_previous_symbol(index) if (prev_symbol in OPERATORS and prev_symbol != ')' - or prev_symbol in COMPARISON_SYMBOLS): + or prev_symbol in COMPARISON_SYMBOLS): return True return False From 010a7a2b58b2994fe3087c5127058a9080cdc5fb Mon Sep 17 00:00:00 2001 From: Roma Date: Wed, 19 Dec 2018 01:46:50 +0300 Subject: [PATCH 26/30] Fixed syntax error. --- final_task/pycalc_src/calculator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/final_task/pycalc_src/calculator.py b/final_task/pycalc_src/calculator.py index cdd96cc..070517b 100644 --- a/final_task/pycalc_src/calculator.py +++ b/final_task/pycalc_src/calculator.py @@ -133,7 +133,7 @@ def _is_unary_operator(self, index, symbol): if index <= len(self.expression): prev_symbol = self._get_previous_symbol(index) if (prev_symbol in OPERATORS and prev_symbol != ')' - or prev_symbol in COMPARISON_SYMBOLS): + or prev_symbol in COMPARISON_SYMBOLS): return True return False From 323374e2b3e0f591e217efebdb205380a5a629d3 Mon Sep 17 00:00:00 2001 From: Roma Date: Wed, 19 Dec 2018 21:48:03 +0300 Subject: [PATCH 27/30] Minor fixes. --- final_task/pycalc_src/calculator.py | 4 ++-- final_task/setup.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/final_task/pycalc_src/calculator.py b/final_task/pycalc_src/calculator.py index 070517b..56ebd72 100644 --- a/final_task/pycalc_src/calculator.py +++ b/final_task/pycalc_src/calculator.py @@ -183,7 +183,7 @@ def _process_expression(self): del self.stack[:] def _process_implicit_multiplication(self, index): - """Сhecks for implicit multiplication.""" + """Сheck for implicit multiplication.""" prev_symbol = self._get_previous_symbol(index) if prev_symbol == ')': self.stack.append('*') @@ -214,7 +214,7 @@ def _calculate_operator(self, operator): self._calculate_result(operator_params.function, first_operand, second_operand) def _calculate_result(self, function, first_operand, second_operand=None): - """Calculate function.""" + """Calculate function(operator).""" try: if second_operand is None: result = function(first_operand) diff --git a/final_task/setup.py b/final_task/setup.py index 6f37d3f..3aacba0 100644 --- a/final_task/setup.py +++ b/final_task/setup.py @@ -1,4 +1,4 @@ -from setuptools import setup, find_packages +from setuptools import setup setup( name="pycalc", From b32f1c71df3cdd3a3325979d7f55c10339566480 Mon Sep 17 00:00:00 2001 From: Roma Date: Thu, 20 Dec 2018 23:34:32 +0300 Subject: [PATCH 28/30] Minor fixes. Improved implicit multiplication. Fixed unittests. --- final_task/pycalc_src/calculator.py | 31 +++++++++++---------------- final_task/pycalc_src/preprocessor.py | 10 ++++----- final_task/tests/test_calculator.py | 25 ++++++++++----------- final_task/tests/test_preprocessor.py | 10 +++++---- 4 files changed, 37 insertions(+), 39 deletions(-) diff --git a/final_task/pycalc_src/calculator.py b/final_task/pycalc_src/calculator.py index 56ebd72..44f9f8a 100644 --- a/final_task/pycalc_src/calculator.py +++ b/final_task/pycalc_src/calculator.py @@ -20,12 +20,12 @@ def __init__(self, expression): self.unary_operator = '' self.rpn = [] self.stack = [] - self.__return_code = 1 + self._return_code = 1 def _process_digit(self, index, symbol): """Process digit from expression.""" if self.expression[index - 1] == ' ' and self.number: - raise CalculatorError('invalid syntax', self.__return_code) + raise CalculatorError('invalid syntax', self._return_code) self.number += symbol def _process_number_and_constant(self): @@ -58,7 +58,7 @@ def _process_operator(self, closing_bracket_index): if self.operator: if self.operator not in OPERATORS: - raise CalculatorError('operator not supported', self.__return_code) + raise CalculatorError('operator not supported', self._return_code) self._process_implicit_multiplication(closing_bracket_index - len(self.operator)) @@ -86,7 +86,7 @@ def _process_comparison(self, index, symbol): if self.stack and self.stack[-1] in COMPARISON_SYMBOLS: if self.expression[index - 1] == ' ': - raise CalculatorError('unexpected whitespace', self.__return_code) + raise CalculatorError('unexpected whitespace', self._return_code) self.stack[-1] += symbol else: while self.stack: @@ -143,7 +143,7 @@ def _is_floordiv(self, index, symbol): return symbol == self.expression[index - 1] == '/' return False - def _process_expression(self): + def _prepare_rpn(self): """Process expression to reverse polish notation.""" for index, symbol in enumerate(self.expression): @@ -174,11 +174,12 @@ def _process_expression(self): elif symbol.isalpha() or symbol == '=': self.operator += symbol + self._process_implicit_multiplication(index) self._process_number_and_constant() self.rpn.extend(reversed(self.stack)) if not self.rpn: - raise CalculatorError('not enough data to calculate', self.__return_code) + raise CalculatorError('not enough data to calculate', self._return_code) del self.stack[:] @@ -201,9 +202,9 @@ def _calculate_operator(self, operator): real_params_count = 1 if len(self.stack) < real_params_count: - raise CalculatorError("not enough operand's for function {}".format(operator), self.__return_code) + raise CalculatorError("not enough operand's for function {}".format(operator), self._return_code) elif self.stack and not isinstance(self.stack[-1], (int, float)): - raise CalculatorError("incorrect operand's for function {}".format(operator), self.__return_code) + raise CalculatorError("incorrect operand's for function {}".format(operator), self._return_code) if real_params_count == 1: operand = self.stack.pop() @@ -220,12 +221,8 @@ def _calculate_result(self, function, first_operand, second_operand=None): result = function(first_operand) else: result = function(first_operand, second_operand) - except ZeroDivisionError as e: - raise CalculatorError(e, self.__return_code) - except ArithmeticError as e: - raise CalculatorError(e, self.__return_code) - except Exception as e: - raise CalculatorError(e, self.__return_code) + except (ZeroDivisionError, ArithmeticError, Exception) as e: + raise CalculatorError(e, self._return_code) else: self.stack.append(result) @@ -251,8 +248,6 @@ def _replace_unary_operator(self, unary_operator): def _convert_to_number(self, number): """Convert number characters to number.""" - if not isinstance(number, str): - return 0 return float(number) if '.' in number else int(number) def _get_previous_symbol(self, index): @@ -265,8 +260,8 @@ def _get_previous_symbol(self, index): def calculate(self): """Prepare and calculate expression.""" preprocessor = Preprocessor(self.expression) - self.expression = preprocessor.preprocessing() - self._process_expression() + self.expression = preprocessor.prepare_expression() + self._prepare_rpn() self._calculate_rpn() return self.stack[-1] diff --git a/final_task/pycalc_src/preprocessor.py b/final_task/pycalc_src/preprocessor.py index 94baf50..049bfbf 100644 --- a/final_task/pycalc_src/preprocessor.py +++ b/final_task/pycalc_src/preprocessor.py @@ -11,18 +11,18 @@ class Preprocessor: def __init__(self, expression): self.expression = expression - self.__return_code = 1 + self._return_code = 1 - def preprocessing(self): + def prepare_expression(self): """Prepare expression for calculate.""" if not self.expression: - raise PreprocessingError('expression is empty', self.__return_code) + raise PreprocessingError('expression is empty', self._return_code) if not isinstance(self.expression, str): - raise PreprocessingError('expression is not a string', self.__return_code) + raise PreprocessingError('expression is not a string', self._return_code) if self.expression.count('(') != self.expression.count(')'): - raise PreprocessingError('brackets are not balanced', self.__return_code) + raise PreprocessingError('brackets are not balanced', self._return_code) self.expression = self.expression.lower() diff --git a/final_task/tests/test_calculator.py b/final_task/tests/test_calculator.py index e1fbf9d..16b7259 100644 --- a/final_task/tests/test_calculator.py +++ b/final_task/tests/test_calculator.py @@ -11,6 +11,8 @@ from pycalc_src.calculator import Calculator from pycalc_src.exceptions import BaseCalculatorException +RETURN_CODE = 0 + class TestStringMethods(unittest.TestCase): """Docstring.""" @@ -35,7 +37,7 @@ def test_process_digit__invalid_expressions(self): expression = '1 2 3 4' calc = Calculator(expression) - calc._Calculator__return_code = 0 + calc._return_code = RETURN_CODE calc.number = '1' with self.assertRaises(BaseCalculatorException): @@ -86,7 +88,7 @@ def test_process_operator__invalid_expressions(self): for expression in invalid_expressions: calc = Calculator('') - calc._Calculator__return_code = 0 + calc._return_code = RETURN_CODE calc.unary_operator = expression.unary_operator calc.operator = expression.operator @@ -137,7 +139,7 @@ def test_process_comparison__invalid_expressions(self): for expression in invalid_expressions: calc = Calculator(expression.expression) - calc._Calculator__return_code = 0 + calc._return_code = RETURN_CODE calc.stack = expression.stack with self.assertRaises(BaseCalculatorException): @@ -204,7 +206,7 @@ def test_is_floordiv__valid_expressions(self): else: self.assertFalse(func_result) - def test_process_expression__valid_expressions(self): + def test_prepare_rpn__valid_expressions(self): """Docstring.""" valid_expression = namedtuple('valid_expression', 'expression result_rpn') @@ -221,11 +223,11 @@ def test_process_expression__valid_expressions(self): for expression in valid_expressions: calc = Calculator(expression.expression) - calc._process_expression() + calc._prepare_rpn() self.assertEqual(calc.rpn, expression.result_rpn) - def test_process_expression__invalid_expressions(self): + def test_prepare_rpn__invalid_expressions(self): """Docstring.""" invalid_expression = namedtuple('invalid_expression', 'expression') @@ -234,10 +236,10 @@ def test_process_expression__invalid_expressions(self): for expression in invalid_expressions: calc = Calculator(expression.expression) - calc._Calculator__return_code = 0 + calc._return_code = RETURN_CODE with self.assertRaises(BaseCalculatorException): - calc._process_expression() + calc._prepare_rpn() def test_calculate_operator__valid_expressions(self): """Docstring.""" @@ -265,7 +267,7 @@ def test_calculate_operator__invalid_expressions(self): for expression in invalid_expressions: calc = Calculator(expression.expression) - calc._Calculator__return_code = 0 + calc._return_code = RETURN_CODE calc.stack = expression.stack with self.assertRaises(BaseCalculatorException): @@ -297,7 +299,7 @@ def test_calculate_result__invalid_expressions(self): for expression in invalid_expressions: calc = Calculator(expression.expression) - calc._Calculator__return_code = 0 + calc._return_code = RETURN_CODE with self.assertRaises(BaseCalculatorException): calc._calculate_result(expression.function, expression.first_operand, expression.second_operand) @@ -340,8 +342,7 @@ def test_convert_to_number__valid_expressions(self): valid_expression = namedtuple('valid_expression', 'number result') valid_expressions = [valid_expression('569', 569), valid_expression('789.99', 789.99), - valid_expression('-500.87', -500.87), - valid_expression([], 0) + valid_expression('-500.87', -500.87) ] for expression in valid_expressions: diff --git a/final_task/tests/test_preprocessor.py b/final_task/tests/test_preprocessor.py index 1e8809a..6182d1c 100644 --- a/final_task/tests/test_preprocessor.py +++ b/final_task/tests/test_preprocessor.py @@ -7,11 +7,13 @@ from pycalc_src.preprocessor import Preprocessor from pycalc_src.exceptions import BaseCalculatorException +RETURN_CODE = 0 + class TestStringMethods(unittest.TestCase): """Docstring.""" - def test_preprocessing__valid_expressions(self): + def test_prepare_expression__valid_expressions(self): """Docstring.""" valid_expression = namedtuple('valid_expression', 'expression result') valid_expressions = [valid_expression('TAN(1)', 'tan(1)'), @@ -20,7 +22,7 @@ def test_preprocessing__valid_expressions(self): for expression in valid_expressions: preprocessor = Preprocessor(expression.expression) - func_result = preprocessor.preprocessing() + func_result = preprocessor.prepare_expression() self.assertEqual(func_result, expression.result) @@ -35,10 +37,10 @@ def test_preprocessing__invalid_expressions(self): for expression in invalid_expressions: preprocessor = Preprocessor(expression.expression) - preprocessor._Preprocessor__return_code = 0 + preprocessor._return_code = RETURN_CODE with self.assertRaises(BaseCalculatorException): - result = preprocessor.preprocessing() + result = preprocessor.prepare_expression() def test_clean_repeatable_operators__valid_expressions(self): """Docstring.""" From 663d6c2fe215a24e24c6e14ea58d431d1512e32a Mon Sep 17 00:00:00 2001 From: Roma Date: Thu, 20 Dec 2018 23:40:35 +0300 Subject: [PATCH 29/30] Fix implicit multiplication. --- final_task/pycalc_src/calculator.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/final_task/pycalc_src/calculator.py b/final_task/pycalc_src/calculator.py index 44f9f8a..c088a28 100644 --- a/final_task/pycalc_src/calculator.py +++ b/final_task/pycalc_src/calculator.py @@ -174,7 +174,9 @@ def _prepare_rpn(self): elif symbol.isalpha() or symbol == '=': self.operator += symbol - self._process_implicit_multiplication(index) + if symbol != ')': + self._process_implicit_multiplication(index) + self._process_number_and_constant() self.rpn.extend(reversed(self.stack)) From 301f15194f849dd748284c635c5dc3dbc9992121 Mon Sep 17 00:00:00 2001 From: Roma Date: Thu, 20 Dec 2018 23:47:34 +0300 Subject: [PATCH 30/30] Minor fixes. --- final_task/tests/test_preprocessor.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/final_task/tests/test_preprocessor.py b/final_task/tests/test_preprocessor.py index 6182d1c..9fff770 100644 --- a/final_task/tests/test_preprocessor.py +++ b/final_task/tests/test_preprocessor.py @@ -48,7 +48,8 @@ def test_clean_repeatable_operators__valid_expressions(self): valid_expressions = [valid_expression('--1', '+1'), valid_expression('-+2', '-2'), valid_expression('++2.4', '+2.4'), - valid_expression('-+-+-++++------3', '-3') + valid_expression('-+-+-++++------3', '-3'), + valid_expression('-+-3++', '+3+') ] for expression in valid_expressions: