-
Notifications
You must be signed in to change notification settings - Fork 28
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
Siarhiej Kresik #32
base: master
Are you sure you want to change the base?
Siarhiej Kresik #32
Changes from all commits
79b6f93
1fb4f8a
e1440e9
ccf6979
58f0d9a
355ad34
73f6f1f
248bb2c
f9adf74
4573b75
ecc888b
c7a16ce
9f9ec2f
0a12d15
d05df4d
976bfa3
eae4290
126e16e
27cab52
0963805
a953f28
26e5dd8
8462382
7f72e35
865675d
c22a61b
efe18dd
42b4399
4a9b556
c93d2ed
4c79d20
201e027
b3fba78
6532f01
6b09a29
97c1362
d6975d6
7623899
e09f312
af3bc3d
144245a
1cd9839
d631b95
556517f
594070d
0132c6e
04da939
1d70982
19105f7
1976bca
0f8ce5b
40702b6
4f54fe8
5122e2d
09a8e07
1564b6e
981196d
e3c9d25
a81744a
fa2c4ed
5438c5c
a96d7ff
9a72352
8c8580b
012ee7f
ea56e66
6e01881
f32053e
282e433
26de9dd
148904c
2959910
db263a2
26a39df
e201df6
89fd3a8
bb12b72
cf35b2e
6380667
98f6fab
64cc409
8662e15
674c77b
e459149
993440c
063c0b0
68a0f75
4e500a8
9f42f68
68a1b82
6e02292
45811e3
20a4b57
a7b5d57
b22aaea
34ebad5
e63cf22
020b08e
6e23697
bc6bbac
c25ca31
e1b5681
d405e51
9199aa7
e77a0ae
e5e1707
7a2d453
88bb1a3
ea176b5
3606210
ea181fb
14ea74e
e3ca8f2
906643f
af92b44
ce12efb
3a90591
2f96036
ca593c3
aadd3af
2d01b3b
1a30d9c
1d8f530
a43a92c
7f0bff6
ab0ca02
29715f3
05e3e74
61848bc
e9a06fb
32f1217
6207d55
4b28491
43c5b86
d04fdd6
4f8c22f
167a9bc
a2e4432
73edc6d
a2a0fbe
1d19055
1fd5bca
0ffe2a8
883f4e7
11d0b01
f29d325
76974cd
9231382
31f1f61
5ee52e8
2d3276a
43f8e3b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
# pycalc | ||
|
||
[![Build Status](https://travis-ci.org/siarhiejkresik/Epam-2019-Python-Homework.svg?branch=master)](https://travis-ci.org/siarhiejkresik/Epam-2019-Python-Homework) | ||
Python Programming Language Foundation Hometask (EPAM, 2019). | ||
For task description see [link](https://github.com/siarhiejkresik/Epam-2019-Python-Homework/tree/master/final_task). | ||
|
||
`pycalc` is a command-line calculator implemented in pure Python 3 using Top Down Operator Precedence parsing algorithm (Pratt parser). It receives mathematical expression string as an argument and prints evaluated result. | ||
|
||
## Features | ||
|
||
`pycalc` supports: | ||
|
||
- arithmetic operations (`+`, `-`, `*`, `/`, `//`, `%`, `^` (`^` is a power)); | ||
- comparison operations (`<`, `<=`, `==`, `!=`, `>=`, `>`); | ||
- 2 built-in python functions: `abs` and `round`; | ||
- all functions and constants from standard python module `math` (trigonometry, logarithms, etc.); | ||
- functions and constants from the modules provided with `-m` or `--use-modules` command-line option; | ||
- exit with non-zero exit code on errors. | ||
|
||
## How to install | ||
|
||
1. `git clone <repository_url>` | ||
2. `cd <repository_name>/final_task/` | ||
3. `pip3 install --user .` or `sudo -H pip3 install .` | ||
|
||
## Examples | ||
|
||
### Command line interface: | ||
|
||
```shell | ||
$ pycalc --help | ||
usage: pycalc [-h] EXPRESSION [-m MODULE [MODULE ...]] | ||
|
||
Pure-python command-line calculator. | ||
|
||
positional arguments: | ||
EXPRESSION expression string to evaluate | ||
|
||
optional arguments: | ||
-h, --help show this help message and exit | ||
-m MODULE [MODULE ...], --use-modules MODULE [MODULE ...] | ||
additional modules to use | ||
``` | ||
|
||
### Calculation: | ||
|
||
```shell | ||
$ pycalc '2+2*2' | ||
6 | ||
|
||
$ pycalc '2+sin(pi)^(2-cos(e))' | ||
2.0 | ||
``` | ||
|
||
```shell | ||
$ pycalc '5+3<=1' | ||
False | ||
``` | ||
|
||
```shell | ||
$ pycalc 'e + pi + tau' | ||
12.143059789228424 | ||
|
||
$ pycalc '1 + inf' | ||
inf | ||
|
||
$ pycalc '1 - inf' | ||
-inf | ||
|
||
$ pycalc 'inf - inf' | ||
nan | ||
|
||
$ pycalc 'nan == nan' | ||
False | ||
``` | ||
|
||
### Errors: | ||
|
||
```shell | ||
$ pycalc '15*(25+1' | ||
ERROR: syntax error | ||
15*(25+1 | ||
^ | ||
$ pycalc 'func' | ||
ERROR: syntax error | ||
func | ||
^ | ||
$ pycalc '10 + 1/0 -3' | ||
ERROR: division by zero | ||
10 + 1/0 -3 | ||
^ | ||
$ pycalc '1 + sin(1,2) - 2' | ||
ERROR: sin() takes exactly one argument (2 given) | ||
1 + sin(1,2) - 2 | ||
^ | ||
$ pycalc '10^10^10' | ||
ERROR: math range error | ||
10^10^10 | ||
^ | ||
$ pycalc '(-1)^0.5' | ||
ERROR: math domain error | ||
(-1)^0.5 | ||
^ | ||
$ pycalc '' | ||
ERROR: empty expression provided | ||
|
||
$ pycalc '1514' -m fake calendar nonexistent time | ||
ERROR: no module(s) named fake, nonexistent | ||
``` | ||
|
||
### Additional modules: | ||
|
||
```python | ||
# my_module.py | ||
def sin(number): | ||
return 42 | ||
``` | ||
|
||
```shell | ||
$ pycalc 'sin(pi/2)' | ||
1.0 | ||
$ pycalc 'sin(pi/2)' -m my_module | ||
42 | ||
$ pycalc 'THURSDAY' -m calendar | ||
3 | ||
$ pycalc 'sin(pi/2) - THURSDAY * 10' -m my_module calendar | ||
12 | ||
``` | ||
|
||
## References | ||
|
||
- https://en.wikipedia.org/wiki/Pratt_parser | ||
- https://tdop.github.io/ | ||
- http://www.oilshell.org/blog/2017/03/31.html | ||
- https://engineering.desmos.com/articles/pratt-parser |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
.DEFAULT_GOAL := run | ||
|
||
PROGRAMM_NAME := pycalc | ||
SRC_FOLDER := $(PROGRAMM_NAME) | ||
INSTALLED_EXECUTABLE_DIR := ~/.local/bin | ||
|
||
PYTHON := python3 | ||
|
||
PIP := pip3 | ||
PIP_LOCAL := --user | ||
|
||
run: | ||
$(INSTALLED_EXECUTABLE_DIR)/$(PROGRAMM_NAME) | ||
|
||
run-source: | ||
$(PYTHON) -m $(SRC_FOLDER) | ||
|
||
install: | ||
@echo "Installing..." | ||
$(PIP) install $(PIP_LOCAL) . | ||
|
||
install-dev: | ||
@echo "Installing in the development mode..." | ||
$(PIP) install $(PIP_LOCAL) --editable . | ||
|
||
uninstall: | ||
@echo "Uninstalling..." | ||
$(PIP) uninstall $(PROGRAMM_NAME) -y | ||
|
||
show: | ||
$(PIP) show $(PROGRAMM_NAME) | ||
|
||
pycodestyle: | ||
pycodestyle $(PROGRAMM_NAME)/* |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
""" | ||
Pure-python implementation of a command-line calculator. | ||
|
||
Receives mathematical expression string as an argument | ||
and prints evaluated result. | ||
""" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
""" | ||
Pure-python implementation of a command-line calculator. | ||
|
||
Receives mathematical expression string as an argument | ||
and prints evaluated result. | ||
""" | ||
|
||
from pycalc.cli import Cli | ||
|
||
|
||
def main(): | ||
"""Pure-python implementation of a command-line calculator.""" | ||
|
||
Cli().run() | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
""" | ||
Parse command-line options. | ||
""" | ||
|
||
import argparse | ||
|
||
PARSER = { | ||
'description': 'Pure-python command-line calculator.' | ||
} | ||
|
||
EXPRESSION = { | ||
'name_or_flags': ['expression'], | ||
'keyword_arguments': { | ||
'metavar': 'EXPRESSION', | ||
'type': str, | ||
'help': 'expression string to evaluate' | ||
} | ||
} | ||
|
||
MODULE = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Я не вижу большого смысла в вынесении параметров для |
||
'name_or_flags': ['-m', '--use-modules'], | ||
'keyword_arguments': { | ||
'metavar': 'MODULE', | ||
'type': str, | ||
'nargs': '+', | ||
'help': 'additional modules to use', | ||
'dest': 'modules' | ||
} | ||
} | ||
|
||
ARGUMENTS = ( | ||
EXPRESSION, | ||
MODULE, | ||
) | ||
|
||
|
||
def get_args(): | ||
"""Parse command line arguments.""" | ||
|
||
parser = argparse.ArgumentParser(**PARSER) | ||
|
||
for arg in ARGUMENTS: | ||
parser.add_argument(*arg['name_or_flags'], **arg['keyword_arguments']) | ||
|
||
args = parser.parse_args() | ||
|
||
return args |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
""" | ||
The calculator package. | ||
""" | ||
|
||
from .calculator import calculator | ||
from .errors import ( | ||
CalculatorError, | ||
CalculatorInitializationError, | ||
CalculatorCalculationError | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
""" | ||
Initialization of a calculator. Return a calculator instance. | ||
""" | ||
|
||
from pycalc.lexer import Lexer | ||
from pycalc.parser import Parser, ParserGenericError | ||
from pycalc.importer import ModuleImportErrors | ||
|
||
from .formatters import err_msg_with_ctx_formatter, err_modules_import_formatter | ||
from .errors import CalculatorCalculationError, CalculatorInitializationError, get_err_msg | ||
from .importer import build_modules_registry | ||
from .matchers import build_matchers | ||
from .messages import ( | ||
CALCULATOR_INITIALIZATION_ERROR, | ||
CANT_PARSE_EXPRESSION, | ||
EMPTY_EXPRESSION_PROVIDED, | ||
) | ||
from .precedence import Precedence | ||
from .specification import build_specification | ||
|
||
|
||
class Calculator: | ||
""" | ||
The calculator class. | ||
|
||
Provide a method to calculate an expression from a string. | ||
""" | ||
|
||
def __init__(self, parser): | ||
self._parser = parser | ||
|
||
def calculate(self, expression): | ||
""" | ||
Calculate an expression. | ||
|
||
Return result of calculation or raise a `CalculatorCalculationError` exception | ||
if calculation fails. | ||
""" | ||
|
||
# empty expression | ||
if not expression: | ||
raise CalculatorCalculationError(EMPTY_EXPRESSION_PROVIDED) | ||
|
||
# calculate an expression | ||
try: | ||
result = self._parser.parse(expression) | ||
return result | ||
|
||
# handle calculation errors | ||
except ParserGenericError as exc_wrapper: | ||
|
||
# ’unwrap’ an original exception and get context | ||
exc = exc_wrapper.__cause__ | ||
ctx = exc.ctx if hasattr(exc, 'ctx') else exc_wrapper.ctx | ||
|
||
# an error message | ||
err_msg = get_err_msg(exc) | ||
err_msg = err_msg_with_ctx_formatter(err_msg, ctx) | ||
|
||
# handle all other errors | ||
except Exception as exc: | ||
err_msg = CANT_PARSE_EXPRESSION | ||
|
||
raise CalculatorCalculationError(err_msg) | ||
|
||
|
||
def calculator(modules_names=None): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Нэйминг немного запутывает. В данном модуле есть класс |
||
""" | ||
Initialize a calculator and return a Calculator instance. | ||
|
||
Raise a `CalculatorInitializationError` exception when initialization fails.""" | ||
|
||
try: | ||
# import constants and functions from default and requested modules | ||
modules_registry = build_modules_registry(modules_names) | ||
|
||
# build lexemes matchers | ||
matchers = build_matchers(modules_registry) | ||
|
||
# create a lexer | ||
lexer = Lexer(matchers) | ||
|
||
# build a specification for a parser | ||
spec = build_specification(modules_registry) | ||
|
||
# create a parser | ||
power = Precedence.DEFAULT | ||
parser = Parser(spec, lexer, power) | ||
|
||
# create a calculator | ||
calculator_ = Calculator(parser) | ||
|
||
return calculator_ | ||
|
||
except ModuleImportErrors as exc: | ||
modules_names = exc.modules_names | ||
err_msg = err_modules_import_formatter(modules_names) | ||
|
||
except Exception: | ||
err_msg = CALCULATOR_INITIALIZATION_ERROR | ||
|
||
raise CalculatorInitializationError(err_msg) |
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.
С моей точки зрения в данном случае можно было обойтись без makefile.
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.
Мне так было зручней ставіць/выдаляць праграму, правяраць, як яна працуе, запускаць pycodestyle.