-
Notifications
You must be signed in to change notification settings - Fork 27
Tatsiana Kavalchuk #42
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
p4elopuh
wants to merge
41
commits into
PythonAndGoCourses2:master
Choose a base branch
from
p4elopuh:p4elopuh-patch-1
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
41 commits
Select commit
Hold shift + click to select a range
6b776f1
Add files via upload
p4elopuh 7285b24
Update __init__.py
p4elopuh d13aa1d
Update setup.py
p4elopuh 74fa1a6
Add files via upload
p4elopuh d9ec494
Add files via upload
p4elopuh c55c813
Delete pycalc.py
p4elopuh 6175f35
Update setup.py
p4elopuh 0700309
Update pycalc.py
p4elopuh 4af3546
Update __init__.py
p4elopuh fb127ba
Add files via upload
p4elopuh 5364371
Change setup
p4elopuh 59250ce
Update __init__
p4elopuh d082f67
Add files via upload
p4elopuh a5b7474
Change parsing function
p4elopuh 93323fc
Remove blanc spaces
p4elopuh cb00b2c
Change parse logic
p4elopuh 6ca42f7
Add files via upload
p4elopuh 9ea6e16
Add files via upload
p4elopuh 8594cba
Delete test.py
p4elopuh ab336a5
Add files via upload
p4elopuh dc6f276
Delete test.py
p4elopuh d3d6280
Add files via upload
p4elopuh 2bee0c5
Update __init__.py
p4elopuh 8ddfab5
Add files via upload
p4elopuh 4871ee0
Update test.py
p4elopuh 0c77a3a
Delete test.py
p4elopuh cda5c65
Update __init__.py
p4elopuh ac0a368
Add files via upload
p4elopuh 5e94371
Add files via upload
p4elopuh 05b9951
Update test.py
p4elopuh 7ddb7fc
Update test.py
p4elopuh ad7a25b
Add test
p4elopuh d2ba285
Add files via upload
p4elopuh 84612d7
Add tests
p4elopuh a5a89fd
Add files via upload
p4elopuh b60c3ff
Add tests
p4elopuh 18bd110
Delete pycalc.py
p4elopuh dd180ca
Add files via upload
p4elopuh b5004fe
Add tests
p4elopuh 09864e8
Add files via upload
p4elopuh 5cb89ac
Add PEP8, modules
p4elopuh File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
name = "pycalc" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
#!/usr/bin/env python3 | ||
|
||
mag = 42 | ||
|
||
|
||
def magn(arg1, arg2, arg3): | ||
if not arg1: | ||
arg1 = mag | ||
return arg1 + arg2 + arg3 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
#!/usr/bin/env python3 | ||
|
||
|
||
def sin(number): | ||
return 42 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,343 @@ | ||
#!/usr/bin/env python3 | ||
|
||
import argparse | ||
import operator | ||
import math | ||
import importlib | ||
|
||
OPERATORS = '+-*/^%<>=! ()' | ||
CONSTANTS = {attr: getattr(math, attr) for attr in dir(math) if isinstance(getattr(math, attr), (int, float))} | ||
OPERATIONS = { | ||
'+': (operator.add, 3), | ||
'-': (operator.sub, 3), | ||
'*': (operator.mul, 4), | ||
'//': (operator.floordiv, 4), | ||
'^': (operator.pow, 5), | ||
'/': (operator.truediv, 4), | ||
'%': (operator.mod, 4), | ||
'<': (operator.lt, 2), | ||
'<=': (operator.le, 2), | ||
'==': (operator.eq, 1), | ||
'!=': (operator.ne, 1), | ||
'>=': (operator.ge, 2), | ||
'>': (operator.gt, 2), | ||
'-u': (operator.neg, 5), | ||
'+u': (lambda x: x, 5), | ||
} | ||
FUNCTIONS = {attr: getattr(math, attr) for attr in dir(math) if not attr[0] == '_'} | ||
FUNCTIONS['abs'] = abs | ||
FUNCTIONS['round'] = round | ||
|
||
|
||
def parse_command_line(): | ||
parser = argparse.ArgumentParser(description='Pure-python command-line calculator.') | ||
parser.add_argument("EXPRESSION", type=str, help='expression string to evaluate') | ||
parser.add_argument('-m', '--use-modules', nargs='*', dest="MODULE", type=str, help='additional modules to use') | ||
args = parser.parse_args() | ||
expression = args.EXPRESSION | ||
modules = args.MODULE | ||
return expression, modules | ||
|
||
|
||
def parse_to_list(exprstr): | ||
""" Converting the expression from string to list by moving tokens from the left side of the string | ||
with their simultaneous assignment to numbers, operators or functions | ||
""" | ||
temp = '' | ||
expr_list = [] | ||
while exprstr: | ||
symbol = exprstr[0] | ||
if symbol in OPERATIONS or (len(exprstr) > 1 and exprstr[:2] in OPERATIONS): # if operator | ||
if exprstr[:2] in OPERATIONS: # if two symbol operator | ||
symbol = exprstr[:2] | ||
expr_list.append(symbol) | ||
exprstr = exprstr[2:] | ||
continue | ||
expr_list.append(symbol) | ||
exprstr = exprstr[1:] | ||
elif symbol.isdigit() or symbol == '.': # if digit or start float number | ||
for symbol_temp in exprstr: # check next symbols to find whole number | ||
if symbol_temp.isdigit() or symbol_temp == '.': # if float | ||
temp += symbol_temp | ||
else: | ||
break | ||
if '.' in temp: | ||
try: | ||
expr_list.append(float(temp)) | ||
except ValueError: # if impossible to convert to float | ||
raise ValueError('error in input number {0}'.format(temp)) | ||
else: | ||
expr_list.append(int(temp)) | ||
exprstr = exprstr[len(temp):] | ||
temp = '' | ||
elif symbol.isalpha(): # if character is alphabetic | ||
for symbol_temp in exprstr: # check the next symbols to find func or constant | ||
if symbol_temp.isalpha() or symbol_temp.isdigit(): | ||
temp += symbol_temp # if alphabetic or digit (for funcs with digit in name) | ||
else: | ||
break # function name must contain only letters and digits | ||
if temp in CONSTANTS: | ||
expr_list.append(temp) | ||
elif temp in FUNCTIONS: | ||
try: | ||
if exprstr[len(temp)] == '(': | ||
expr_list.append(temp) | ||
else: | ||
raise SyntaxError('missed argument(s) for function {0}'.format(temp)) | ||
except IndexError: | ||
raise IndexError('missed argument(s) for function {0}'.format(temp)) | ||
else: # if temp not in constants or function_dict | ||
raise ValueError('unknown function or constant {0}'.format(temp)) | ||
exprstr = exprstr[len(temp):] | ||
temp = '' | ||
elif symbol in ('(', ')', ','): # if bracket or comma - move to list | ||
expr_list.append(symbol) | ||
exprstr = exprstr[1:] | ||
elif symbol == ' ': | ||
exprstr = exprstr[1:] # ignore space | ||
else: # if different symbol - raise Error | ||
raise SyntaxError('unsupported symbol "{0}" in expression'.format(symbol)) | ||
return expr_list | ||
|
||
|
||
def check_unary_operator(expr_list): | ||
""" Checking the '+' and '-' operators for unary conditions and their modifying. | ||
Conditions for assignment to the unary group - '-' or '+' stay on first position in equation or | ||
after another arithmetic operator or opening parentheses or comma. | ||
""" | ||
if expr_list[0] in ('-', '+'): | ||
expr_list[0] += 'u' | ||
for index in range(1, len(expr_list)): | ||
if expr_list[index] in ('-', '+') and (expr_list[index - 1] in ('(', ',') or | ||
expr_list[index-1] in OPERATIONS): | ||
expr_list[index] += 'u' | ||
|
||
|
||
def precedence(oper1, oper2): | ||
""" Determining of operator precedence """ | ||
left_associated = ['+', '-', '*', '//', '/', '%', '<', '<=', '==', '!=', '>=', '>'] | ||
right_associated = ['^', '-u', '+u'] | ||
if oper1 in left_associated: | ||
return OPERATIONS[oper1][1] <= OPERATIONS[oper2][1] | ||
elif oper1 in right_associated: | ||
return OPERATIONS[oper1][1] < OPERATIONS[oper2][1] | ||
|
||
|
||
def shunting_yard_alg(expr_list): | ||
""" Parsing mathematical expressions specified in infix notation to Reverse Polish Notation | ||
by Shunting-Yard algorithm. | ||
RPN - notation of equation in which operator or function stay after corresponding operand(s). | ||
Operators are added according their precedence. No parentheses are needed. | ||
Resulting notation is ready for calculation (from left to right). | ||
""" | ||
output_list = [] | ||
stack = [] | ||
while expr_list: | ||
item = expr_list[0] | ||
if isinstance(item, (int, float)) or item in CONSTANTS: | ||
output_list.append(item) | ||
expr_list = expr_list[1:] | ||
elif item in FUNCTIONS: | ||
stack.append(item) | ||
expr_list = expr_list[1:] | ||
elif item == ',': | ||
expr_list = expr_list[1:] | ||
if '(' in stack: | ||
while stack[-1] != '(': | ||
output_list.append(stack.pop()) | ||
else: | ||
raise SyntaxError('the opening parentheses or comma is missed') | ||
output_list.append(item) # comma will indicate the presence of multi parameters for the func | ||
if expr_list and expr_list[0] == ')': | ||
raise SyntaxError('error in the data inside the parentheses') | ||
elif item in OPERATIONS: | ||
if stack and (stack[-1] in OPERATIONS): | ||
while stack and (stack[-1] in OPERATIONS): | ||
if precedence(item, stack[-1]): | ||
output_list.append(stack.pop()) | ||
else: | ||
break | ||
stack.append(item) | ||
elif stack == [] or '(' in stack: | ||
stack.append(item) | ||
expr_list = expr_list[1:] | ||
elif item == '(': | ||
stack.append(item) | ||
expr_list = expr_list[1:] | ||
elif item == ')': | ||
while stack and stack[-1] != '(': | ||
output_list.append(stack.pop()) | ||
if stack and stack[-1] == '(': | ||
stack.pop() | ||
elif not stack: | ||
raise SyntaxError('opening parentheses is missed') | ||
if stack and (stack[-1] in FUNCTIONS): | ||
output_list.append(stack.pop()) | ||
expr_list = expr_list[1:] | ||
else: | ||
while stack: | ||
if stack[-1] == '(': | ||
raise SyntaxError('closing parentheses is missed') | ||
output_list.append(stack.pop()) | ||
return output_list # returning expression in Reverse Polish Notation | ||
|
||
|
||
def perform_operation(operator, operand_1, operand_2): | ||
""" Perform binary operator for operands """ | ||
return OPERATIONS[operator][0](operand_1, operand_2) | ||
|
||
|
||
def perform_unary_operation(operator, operand): | ||
""" Perform unary operator for operand """ | ||
return OPERATIONS[operator][0](operand) | ||
|
||
|
||
def perform_function(function, *operand): | ||
""" Perform function with one or more arguments """ | ||
try: | ||
result = FUNCTIONS[function](*operand) | ||
except ValueError: | ||
raise ValueError('error in entered data for function {0}'.format(function)) | ||
# except TypeError: | ||
# result = function_dict[function](operand) | ||
# raise TypeError('unsupported data in function {0} parentheses'.format(function)) | ||
return result | ||
|
||
|
||
def calculation_from_rpn(rev_pol_not_list): | ||
""" Sequential calculation from left to right of expression in Reverse Polish Notation. | ||
Operators and functions are applied to corresponding previous staying operand(s) | ||
""" | ||
stack = [] | ||
arg_stack = [] | ||
for item in rev_pol_not_list: | ||
if item in CONSTANTS: | ||
stack.append(CONSTANTS[item]) | ||
elif isinstance(item, (int, float)): | ||
stack.append(item) | ||
elif item == ',': | ||
arg_stack.append(stack.pop()) | ||
stack.append(item) | ||
elif item in FUNCTIONS: | ||
if len(stack) > 1 and stack[-2] == ',': | ||
commas = 0 # cheking the presence of commas and sum all successive ones | ||
for index in range(-2, -len(stack)-1, -1): | ||
if stack[index] == ',': | ||
commas += 1 | ||
else: | ||
break | ||
while commas: | ||
params = arg_stack[-commas:] | ||
params.append(stack[-1]) | ||
try: | ||
intermediate_result = perform_function(item, *params) | ||
for index in range(commas + 1): | ||
stack.pop() | ||
for index in range(commas): | ||
arg_stack.pop() | ||
stack.append(intermediate_result) | ||
break | ||
except TypeError: | ||
commas -= 1 | ||
continue | ||
else: | ||
try: | ||
intermediate_result = perform_function(item, stack.pop()) | ||
stack.append(intermediate_result) | ||
except TypeError: | ||
raise TypeError("unsupported operand(s)") | ||
else: | ||
try: | ||
intermediate_result = perform_function(item, stack.pop()) | ||
stack.append(intermediate_result) | ||
except TypeError: | ||
raise TypeError("unsupported operand for function '{0}'".format(item)) | ||
except OverflowError: | ||
raise OverflowError(f'result of {item} too large to be represented') | ||
elif item in OPERATIONS: | ||
if item in ('-u', '+u'): | ||
intermediate_result = perform_unary_operation(item, stack.pop()) | ||
else: | ||
try: | ||
operand_2, operand_1 = stack.pop(), stack.pop() | ||
intermediate_result = perform_operation(item, operand_1, operand_2) | ||
except ZeroDivisionError: | ||
raise ZeroDivisionError('division by zero') | ||
except IndexError: | ||
raise IndexError('insufficient amount of operands') | ||
except OverflowError: | ||
raise OverflowError(f'result of {item} too large to be represented') | ||
stack.append(intermediate_result) | ||
if len(stack) == 1: | ||
result = stack[0] | ||
return result | ||
else: | ||
raise SyntaxError('insufficient amount of operators or function or too many operands/arguments') | ||
|
||
|
||
def check_empty_operators(exprstr): | ||
""" Checking expression for empty string or string containing only operators or parentheses """ | ||
if len(exprstr) == 0: | ||
raise SyntaxError('empty expression') | ||
elif set(exprstr).issubset(set(OPERATORS)): | ||
raise SyntaxError('no digits, constants or functions in expression') | ||
else: | ||
return False | ||
|
||
|
||
def check_brackets(exprstr): | ||
""" Checking for brackets balance """ | ||
if ('(' and ')') in exprstr: | ||
if exprstr.count('(') == exprstr.count(')'): | ||
if exprstr.index(')') < exprstr.index('('): | ||
raise SyntaxError('brackets are not balanced') | ||
return True | ||
else: | ||
raise SyntaxError('brackets are not balanced') | ||
else: | ||
return True | ||
|
||
|
||
def calculation(exprstr): | ||
""" Calculation of string type argument from command line """ | ||
if (not check_empty_operators(exprstr)) and check_brackets(exprstr): | ||
expr_list = parse_to_list(exprstr) | ||
check_unary_operator(expr_list) | ||
return calculation_from_rpn(shunting_yard_alg(expr_list)) | ||
|
||
|
||
def import_new_module(module_name): | ||
""" Trying to import module provided with -m (--use-modules) flag and | ||
updating the constants and functions dictionaries. | ||
Functions and constants from user defined modules have higher priority. | ||
in case of name conflict then stuff from math module or built-in functions | ||
If the module cannot be imported info about this will be printed | ||
but calculation will be tried to execute | ||
""" | ||
try: | ||
module = importlib.import_module(module_name) | ||
try: | ||
CONSTANTS.update({attr: getattr(module, attr) for attr in dir(module) | ||
if isinstance(getattr(module, attr), (int, float))}) | ||
FUNCTIONS.update({attr: getattr(module, attr) for attr in dir(module) if not attr[0] == '_'}) | ||
except Exception: | ||
print(f'Smth bad with new module {module_name}') | ||
pass | ||
# raise Exception('Smth bad with new module') | ||
except ImportError: | ||
print(f'Unable to import the module {module_name}') | ||
|
||
|
||
def main(): | ||
try: | ||
expression, modules = parse_command_line() | ||
if modules: | ||
for module in modules: | ||
import_new_module(module) | ||
print(calculation(expression)) | ||
except Exception as err: | ||
print('ERROR: ', err) | ||
|
||
|
||
if __name__ == '__main__': | ||
main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
name = 'pycalc' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import setuptools | ||
|
||
|
||
setuptools.setup( | ||
name="pycalc", | ||
version="0.1", | ||
author="Tatsiana Kavalchuk", | ||
author_email="p4elopuh@gmail.com", | ||
description="Pure-python command-line calculator", | ||
packages=setuptools.find_packages(), | ||
entry_points={ | ||
'console_scripts': [ | ||
'pycalc=pycalc.pycalc:main', | ||
], | ||
}, | ||
classifiers=[ | ||
"Programming Language :: Python :: 3", | ||
"License :: OSI Approved :: MIT License", | ||
"Operating System :: OS Independent", | ||
], | ||
) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
В чём назначение этой проверки?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Проверить, если в выражении есть скобки, то сбалансировано ли их количество, а также нет ли закрывающей скобки раньше открывающей.
Но после вопроса уже сомневаюсь в необходимости.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
В условиях цейтнота добавила возможность импорта модулей и загрузила тестовые файлы для проверки приоритета функций и констант, и не обратила внимания, что эта функциональность не проверяется скриптом.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Выражение
('(' and ')')
всегда будет возвращать ')'. Таким образомif ('(' and ')') in exprstr
проверяет есть ли в строке)