Skip to content

Commit a7330d3

Browse files
pycalc implementation2
1 parent 1ebaa14 commit a7330d3

File tree

11 files changed

+839
-0
lines changed

11 files changed

+839
-0
lines changed
-13 KB
Binary file not shown.

final_task/pycalc-1.0.0.tar.gz

-8.69 KB
Binary file not shown.

final_task/pycalc/UnitTests.py

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
"""This module contains unit tests for methods from all pycalc modules"""
2+
3+
# general import
4+
import unittest
5+
6+
# import of classes to be tested from pycalc modules
7+
from tokenizer import Tokenizer
8+
from addmultsigns import Multsignsadder
9+
from rpn import RPN
10+
from constsreplacer import Constsreplacer
11+
from rpncalculator import RPNcalculator
12+
13+
14+
# Tests of 'Tokenizer' class from 'tokenizer' module
15+
class TokenizerTestCase(unittest.TestCase):
16+
"""Tests for Tokenizer class"""
17+
18+
def test_extract_operators_and_pos_int_numbers(self):
19+
"""Are operators and positive int numbers extracted properly?"""
20+
user_expr = '1+2-3*4/5^6**7//8%9'
21+
tokenizer = Tokenizer(user_expr)
22+
tokens, error_msg = tokenizer.extract_tokens()
23+
self.assertEqual(tokens, ['1', '+', '2', '-', '3', '*', '4', '/',
24+
'5', '^', '6', '**', '7', '//', '8', '%', '9'])
25+
self.assertEqual(error_msg, None)
26+
27+
def test_extract_operators_and_neg_int_numbers(self):
28+
"""Are operators and negative int numbers extracted properly?"""
29+
user_expr = '-1+-2--3*-4/-5^-6**-7//-8%-9'
30+
tokenizer = Tokenizer(user_expr)
31+
tokens, error_msg = tokenizer.extract_tokens()
32+
self.assertEqual(tokens, ['-1.0', '-', '2', '+', '3', '*', '-4', '/', '-5',
33+
'^', '-6', '**', '-7', '//', '-8', '%', '-9'])
34+
self.assertEqual(error_msg, None)
35+
36+
def test_extract_pos_float_numbers(self):
37+
"""Are positive float numbers extracted properly?"""
38+
user_expr = '0.1+1.55-112.12'
39+
tokenizer = Tokenizer(user_expr)
40+
tokens, error_msg = tokenizer.extract_tokens()
41+
self.assertEqual(tokens, ['0.1', '+', '1.55', '-', '112.12'])
42+
self.assertEqual(error_msg, None)
43+
44+
def test_extract_neg_float_numbers(self):
45+
"""Are negative float numbers extracted properly?"""
46+
user_expr = '-0.1+-1.55--112.12'
47+
tokenizer = Tokenizer(user_expr)
48+
tokens, error_msg = tokenizer.extract_tokens()
49+
self.assertEqual(tokens, ['-0.1', '-', '1.55', '+', '112.12'])
50+
self.assertEqual(error_msg, None)
51+
52+
def test_extract_comparison_operators(self):
53+
"""Are comparison operators extracted properly?"""
54+
user_expr = '><>=<=!==='
55+
tokenizer = Tokenizer(user_expr)
56+
tokens, error_msg = tokenizer.extract_tokens()
57+
self.assertEqual(tokens, ['>', '<', '>=', '<=', '!=', '=='])
58+
self.assertEqual(error_msg, None)
59+
60+
def test_extract_pos_constants(self):
61+
"""Are positive constants extracted properly?"""
62+
user_expr = 'e+pi-tau/inf*nan'
63+
tokenizer = Tokenizer(user_expr)
64+
tokens, error_msg = tokenizer.extract_tokens()
65+
self.assertEqual(tokens, ['e', '+', 'pi', '-', 'tau', '/', 'inf', '*', 'nan'])
66+
self.assertEqual(error_msg, None)
67+
68+
def test_extract_neg_constants(self):
69+
"""Are negative constants extracted properly?"""
70+
user_expr = '-e+-pi--tau/-inf*-nan'
71+
tokenizer = Tokenizer(user_expr)
72+
tokens, error_msg = tokenizer.extract_tokens()
73+
self.assertEqual(tokens, ['-e', '-', 'pi', '+', 'tau', '/', '-inf', '*', '-nan'])
74+
self.assertEqual(error_msg, None)
75+
76+
def test_extract_brackets(self):
77+
"""Are brackets extracted properly?"""
78+
user_expr = '()'
79+
tokenizer = Tokenizer(user_expr)
80+
tokens, error_msg = tokenizer.extract_tokens()
81+
self.assertEqual(tokens, ['(', ')'])
82+
self.assertEqual(error_msg, None)
83+
84+
def test_extract_comma(self):
85+
"""Is comma extracted?"""
86+
user_expr = 'pow(2,3)'
87+
tokenizer = Tokenizer(user_expr)
88+
tokens, error_msg = tokenizer.extract_tokens()
89+
self.assertEqual(tokens, ['pow', '(', '2', ',', '3', ')'])
90+
self.assertEqual(error_msg, None)
91+
92+
def test_extract_functions(self):
93+
"""Are functions extracted properly?"""
94+
user_expr = "round(sin(2)-asin(1))-abs(exp(3))"
95+
tokenizer = Tokenizer(user_expr)
96+
tokens, error_msg = tokenizer.extract_tokens()
97+
self.assertEqual(tokens, ['round', '(', 'sin', '(', '2', ')', '-', 'asin', '(', '1', ')', ')',
98+
'-', 'abs', '(', 'exp', '(', '3', ')', ')'])
99+
self.assertEqual(error_msg, None)
100+
101+
def test_consider_sub_signs_method(self):
102+
"""Are several subtraction and addition signs replaced by one integrated sign?"""
103+
user_expr = '-1---2+-3+++4+-2'
104+
tokenizer = Tokenizer(user_expr)
105+
tokens, error_msg = tokenizer.extract_tokens()
106+
self.assertEqual(tokens, ['-1.0', '-', '2', '-', '3', '+', '4', '-', '2'])
107+
self.assertEqual(error_msg, None)
108+
109+
def test_is_number_method(self):
110+
"""Does 'is_number' method distinguish tokens which are numbers from ones which are not?"""
111+
tokens = ['.3', '-0.3', '7', 'tan']
112+
tokenizer = Tokenizer(user_expr='')
113+
is_numbers = []
114+
for token in tokens:
115+
is_numbers.append(tokenizer.is_number(token))
116+
self.assertEqual(is_numbers, [True, True, True, False])
117+
118+
def test_extract_tokens_error_msg(self):
119+
"""Is error_message created?"""
120+
user_expr = "2+shikaka(3)"
121+
tokenizer = Tokenizer(user_expr)
122+
tokens, error_msg = tokenizer.extract_tokens()
123+
self.assertEqual(error_msg, 'ERROR: invalid syntax')
124+
125+
126+
# Tests of 'Multsignsadder' class from 'addmultsigns' module
127+
class MultsignsadderTestCase(unittest.TestCase):
128+
"""Tests for Multsignsadder class"""
129+
130+
def test_is_number_method(self):
131+
"""Does 'is_number' method distinguish tokens which are numbers from ones which are not?"""
132+
tokens = ['2.3', '-0.6', '5', 'sin', 'exp']
133+
mult_signs_adder = Multsignsadder(tokens)
134+
is_numbers = []
135+
for token in tokens:
136+
is_numbers.append(mult_signs_adder.is_number(token))
137+
self.assertEqual(is_numbers, [True, True, True, False, False])
138+
139+
def test_addmultsigns_add_mult_signs(self):
140+
"""Are multiplication signs added to where they implicit were to be in expression?"""
141+
tokens = ['5', 'tau', '-', '4', 'sin', '(', '7', ')', '-', '9', '(', '1', '+', '10', ')']
142+
mult_signs_adder = Multsignsadder(tokens)
143+
extd_tokens = mult_signs_adder.addmultsigns()
144+
self.assertEqual(extd_tokens, ['5', '*', 'tau', '-', '4', '*', 'sin', '(', '7', ')', '-',
145+
'9', '*', '(', '1', '+', '10', ')'])
146+
147+
def test_addmultsigns_dont_add_mult_signs(self):
148+
"""Aren't multiplication signs added if it's not needed?"""
149+
tokens = ['2', '+', '3', '*', '5']
150+
mult_signs_adder = Multsignsadder(tokens)
151+
extd_tokens = mult_signs_adder.addmultsigns()
152+
self.assertEqual(extd_tokens, ['2', '+', '3', '*', '5'])
153+
154+
def test_consider_neg_funcs_method(self):
155+
"""Are negative functions tokens replaced by '-1*function' tokens?"""
156+
tokens = ['2', '*', '-sin', '(', '2', ')']
157+
mult_signs_adder = Multsignsadder(tokens)
158+
mult_signs_adder.consider_neg_functions(mult_signs_adder.tokens)
159+
self.assertEqual(mult_signs_adder.tokens, ['2', '*', '-1', '*', 'sin', '(', '2', ')'])
160+
161+
def test_consider_log_args_method(self):
162+
"""Is 'e' added as a base for log functions if last was entered with one argument?"""
163+
tokens = ['log', '(', '33', ')']
164+
mult_signs_adder = Multsignsadder(tokens)
165+
mult_signs_adder.consider_log_args(mult_signs_adder.tokens)
166+
self.assertEqual(mult_signs_adder.tokens, ['log', '(', '33', ',', 'e', ')'])
167+
168+
169+
# Tests of 'RPN class' from 'rpn' module
170+
class RPNTestCase(unittest.TestCase):
171+
"""Tests for RPN class"""
172+
173+
def test_is_left_associative_method(self):
174+
"""Are left associative operators recognized?"""
175+
tokens = ['^', '**', '+', '/']
176+
rpn = RPN(tokens)
177+
is_left_associative = []
178+
for token in tokens:
179+
is_left_associative.append(rpn.is_left_associative(token))
180+
self.assertEqual(is_left_associative, [False, False, True, True])
181+
182+
def test_is_number_method(self):
183+
"""Does 'is_number' method distinguish tokens which are numbers from ones which are not?"""
184+
tokens = ['1.3', '-0.5', '/', '%', '9']
185+
rpn = RPN(tokens)
186+
is_numbers = []
187+
for token in tokens:
188+
is_numbers.append(rpn.is_number(token))
189+
self.assertEqual(is_numbers, [True, True, False, False, True])
190+
191+
def test_convert2rpn_method(self):
192+
"""Does 'convert2rpn' method work correctly?"""
193+
tokens = ['-pi', '*', 'round', '(', '2.23', ')', '//', '5', '*', 'pow', '(', '2', '3', ')']
194+
rpn = RPN(tokens)
195+
result, error_msg = rpn.convert2rpn()
196+
self.assertEqual(result, ['-pi', '2.23', 'round', '*', '5', '//', '2', '3', 'pow', '*'])
197+
self.assertEqual(error_msg, None)
198+
199+
def test_convert2rpn_method_error_msg(self):
200+
"""Is error_message created?"""
201+
tokens = ['(', '2', '+', '3', ')', ')']
202+
rpn = RPN(tokens)
203+
result, error_msg = rpn.convert2rpn()
204+
self.assertEqual(error_msg, 'ERROR: brackets are not balanced')
205+
206+
207+
# Tests of 'Constsreplacer' class from 'constsreplacer' module
208+
class ConstsreplacerTestCase(unittest.TestCase):
209+
"""Tests for Constsreplacer class"""
210+
211+
def test_replace_constants_method(self):
212+
"""Are constants replaced and not constants aren't replaced?"""
213+
tokens = ['e', '-e', 'pi', '-pi', 'tau', '-tau', '2', 'cos', 'inf', '-nan', '+']
214+
constsreplacer = Constsreplacer(tokens)
215+
replaced_tokens = constsreplacer.replace_constants()
216+
self.assertEqual(replaced_tokens, ['2.718281828459045', '-2.718281828459045',
217+
'3.141592653589793', '-3.141592653589793',
218+
'6.283185307179586', '-6.283185307179586',
219+
'2', 'cos', 'inf', '-nan', '+'])
220+
221+
222+
# Tests of 'RPNcalculator' class from 'rpncalculator' module
223+
class RPNcalculatorTestCase(unittest.TestCase):
224+
"""Tests for RPNcalculator class"""
225+
226+
def test_evaluate_method_result(self):
227+
"""Does 'evaluate' method actually evaluate RPN math expression and give out correct result?"""
228+
rpn_tokens = ['2', 'sqrt', '3', '/', '3.14', '*', 'tan']
229+
rpncalculator = RPNcalculator(rpn_tokens)
230+
result, error_msg = rpncalculator.evaluate()
231+
self.assertEqual(result, 11.009005500434151)
232+
self.assertEqual(error_msg, None)
233+
234+
def test_evaluate_method_error_msg_zero_division(self):
235+
"""Is 'division by zero' error message created?"""
236+
rpn_tokens = ['2', '0', '/']
237+
rpncalculator = RPNcalculator(rpn_tokens)
238+
result, error_msg = rpncalculator.evaluate()
239+
self.assertEqual(error_msg, 'ERROR: float division by zero')
240+
241+
def test_evaluate_method_error_msg_neg_num_in_fract_pow(self):
242+
"""Is 'negative number cannot be raised to a fractional power' error message created?"""
243+
rpn_tokens = [['-2', '0.5', '**'], ['-2', '0.5', '^']]
244+
error_msgs = []
245+
for rpn_tokens_list in rpn_tokens:
246+
rpncalculator = RPNcalculator(rpn_tokens_list)
247+
error_msgs.append(rpncalculator.evaluate()[1])
248+
for error_msg in error_msgs:
249+
self.assertEqual(error_msg, 'ERROR: negative number cannot be raised to a fractional power')
250+
251+
def test_evaluate_method_error_msg_neg_num_sqrt(self):
252+
"""Is 'root can't be extracted from a negative number' error message created?"""
253+
rpn_tokens = ['-2', 'sqrt']
254+
rpncalculator = RPNcalculator(rpn_tokens)
255+
result, error_msg = rpncalculator.evaluate()
256+
self.assertEqual(error_msg, "ERROR: a root can't be extracted from a negative number")
257+
258+
def test_evaluate_method_error_msg_invalid_syntax(self):
259+
"""Is 'invalid syntax' error message created?"""
260+
rpn_tokens = ['2', '+']
261+
rpncalculator = RPNcalculator(rpn_tokens)
262+
result, error_msg = rpncalculator.evaluate()
263+
self.assertEqual(error_msg, "ERROR: invalid syntax")
264+
265+
266+
if __name__ == '__main__':
267+
unittest.main()

final_task/pycalc/__init__.py

Whitespace-only changes.

final_task/pycalc/addmultsigns.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
"""This module contains a class that allows to take into account implicit multiplication signs"""
2+
3+
# import from pycalc self library
4+
from pycalclib import constants, functions, negative_functions
5+
6+
7+
class Multsignsadder():
8+
"""A model of mult_signs_adder capable of adding implicit multiplications signs in list of tokens"""
9+
10+
def __init__(self, tokens):
11+
"""Initialize mult_signs_adder"""
12+
self.tokens = tokens
13+
self.extended_tokens = []
14+
self.constants = constants
15+
self.functions = functions
16+
self.negative_functions = negative_functions
17+
18+
def is_number(self, token):
19+
"""Determines whether token is a number"""
20+
try:
21+
float(token)
22+
return True
23+
except ValueError:
24+
return False
25+
26+
def consider_neg_functions(self, tokens):
27+
"""Replaces negative functions tokens by '-1*function' tokens"""
28+
index = 0
29+
while index != len(tokens)-1:
30+
if tokens[index] in self.negative_functions:
31+
tokens[index] = tokens[index][1:] # remove '-' sign
32+
tokens.insert(index, '-1')
33+
tokens.insert(index+1, '*')
34+
index += 3
35+
index += 1
36+
37+
def consider_log_args(self, tokens):
38+
"""Adds 'e' as a base for log function explicitly if last was originally entered with one argument"""
39+
for index in range(len(tokens)):
40+
if tokens[index] == 'log':
41+
for index2 in range(index+1, len(tokens)):
42+
if ((tokens[index2] == ')' and index2 == len(tokens)-1)
43+
or (tokens[index2] == ')' and index2 != len(tokens)-1 and tokens[index2+1] != ',')):
44+
log_args = tokens[index+2:index2]
45+
if ',' not in log_args:
46+
tokens.insert(index2, ',')
47+
tokens.insert(index2+1, 'e')
48+
break
49+
50+
def addmultsigns(self):
51+
"""Adds implicit multiplication signs in list of math tokens to where they are supposed to be"""
52+
for index in range(len(self.tokens)-1):
53+
self.extended_tokens.append(self.tokens[index])
54+
if (self.is_number(self.tokens[index]) and ((self.tokens[index+1] in self.constants)
55+
or (self.tokens[index+1] in self.functions)
56+
or (self.tokens[index+1] == '('))):
57+
self.extended_tokens.append('*')
58+
continue
59+
elif self.tokens[index] == ')' and self.tokens[index+1] == '(':
60+
self.extended_tokens.append('*')
61+
continue
62+
self.extended_tokens.append(self.tokens[-1])
63+
64+
self.consider_neg_functions(self.extended_tokens)
65+
self.consider_log_args(self.extended_tokens)
66+
67+
return self.extended_tokens
68+
69+
70+
if __name__ == '__main__':
71+
print("""This module contains class that allows to insert multiplications signs to where they where supposed
72+
to be in a list with math tokens. For example: \n""")
73+
test_tokens = ['-0.1', 'tan', '+', '23', '*', '-sin', '(', '3', ')', '/', '.12', 'e']
74+
mult_signs_adder = Multsignsadder(test_tokens)
75+
extended_tokens = mult_signs_adder.addmultsigns()
76+
print('Original tokens: ', test_tokens)
77+
print('Extended tokens: ', extended_tokens)
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"""This module contains a class that allows to replace constants by their numeric equivalents"""
2+
3+
# import from pyalc self library
4+
from pycalclib import constants_numeric_equivalents
5+
6+
7+
class Constsreplacer():
8+
"""A model of constants replacer capable of replacing constants (from math module) by their numeric equivalents"""
9+
10+
def __init__(self, rpn_tokens):
11+
"""Initialize constsreplacer"""
12+
self.rpn_tokens = rpn_tokens
13+
self.constants_numeric_equivalents = constants_numeric_equivalents
14+
15+
def replace_constants(self):
16+
"""Replaces tokens which are math module constants by their numeric equivalent"""
17+
for index in range(len(self.rpn_tokens)):
18+
if self.rpn_tokens[index] in self.constants_numeric_equivalents.keys():
19+
self.rpn_tokens[index] = str(self.constants_numeric_equivalents[self.rpn_tokens[index]])
20+
21+
return self.rpn_tokens
22+
23+
24+
if __name__ == '__main__':
25+
print("""This module contains class that allows to replace tokens which are math module constants by their
26+
numeric equivalents. For example: \n""")
27+
test_tokens = ['2', '*', 'nan', '-', '-inf', '+', '-tau', '*', '-pi', '+', 'e']
28+
print('RPN tokens with constants: ', test_tokens)
29+
constsreplacer = Constsreplacer(test_tokens)
30+
rpn_tokens = constsreplacer.replace_constants()
31+
print('RPN tokens after replacement of constants: ', rpn_tokens)

0 commit comments

Comments
 (0)