Skip to content
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

Open
wants to merge 152 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
152 commits
Select commit Hold shift + click to select a range
79b6f93
Add setup.py to the project
siarhiejkresik Apr 11, 2019
1fb4f8a
Add Makefile
siarhiejkresik Apr 11, 2019
e1440e9
Change the program name in the setup.py
siarhiejkresik Apr 11, 2019
ccf6979
setup.py
siarhiejkresik Apr 11, 2019
58f0d9a
Makefile
siarhiejkresik Apr 11, 2019
355ad34
Makefile
siarhiejkresik Apr 11, 2019
73f6f1f
Args
siarhiejkresik Apr 11, 2019
248bb2c
Implement importing members from a module
siarhiejkresik Apr 26, 2019
f9adf74
Update importing from a module
siarhiejkresik Apr 26, 2019
4573b75
Change members names storing from set to list
siarhiejkresik Apr 26, 2019
ecc888b
Refactor, fix a bug, add exception handling
siarhiejkresik Apr 26, 2019
c7a16ce
Add a function checker
siarhiejkresik Apr 26, 2019
9f9ec2f
Format code
siarhiejkresik Apr 26, 2019
0a12d15
Rename imports.py file to importer.py
siarhiejkresik Apr 26, 2019
d05df4d
Add lexer
siarhiejkresik Apr 27, 2019
976bfa3
Add helpers functions for the matcher package
siarhiejkresik Apr 29, 2019
eae4290
Add the numbers matcher
siarhiejkresik Apr 29, 2019
126e16e
Add the Matchers class
siarhiejkresik Apr 29, 2019
27cab52
Add __init__.py for the matcher package
siarhiejkresik Apr 29, 2019
0963805
Add setup.py to the project
siarhiejkresik Apr 11, 2019
a953f28
Add Makefile
siarhiejkresik Apr 11, 2019
26e5dd8
Refactor lexer
siarhiejkresik Apr 29, 2019
8462382
Refactor lexer
siarhiejkresik Apr 30, 2019
7f72e35
Add parser
siarhiejkresik Apr 30, 2019
865675d
Update lexer
siarhiejkresik Apr 30, 2019
c22a61b
Add lexer tests
siarhiejkresik Apr 30, 2019
efe18dd
Refactor lexer
siarhiejkresik Apr 30, 2019
42b4399
Move code blocks in the parser class
siarhiejkresik May 2, 2019
4a9b556
Refactor lexer with comsumer/peek
siarhiejkresik May 3, 2019
c93d2ed
Fix tests for lexer
siarhiejkresik May 3, 2019
4c79d20
Refactor a parser with consume, peek methods
siarhiejkresik May 3, 2019
201e027
Revert lexer init method
siarhiejkresik May 3, 2019
b3fba78
Refactor parser - introduce spec
siarhiejkresik May 4, 2019
6532f01
Clear token wapper in init method
siarhiejkresik May 5, 2019
6b09a29
Add the format method to the lexer
siarhiejkresik May 5, 2019
97c1362
Modify matchers registering
siarhiejkresik May 5, 2019
d6975d6
Add peek and check method to the parser
siarhiejkresik May 5, 2019
7623899
Add some debug info to the parser
siarhiejkresik May 5, 2019
e09f312
Refactor advance method of the parser
siarhiejkresik May 5, 2019
af3bc3d
Update asserts in the lexer module
siarhiejkresik May 5, 2019
144245a
Handle exceptions in the parser module
siarhiejkresik May 5, 2019
1cd9839
Add the init file for the specification package
siarhiejkresik May 5, 2019
d631b95
Implement nud and led classes for the parser specification
siarhiejkresik May 5, 2019
556517f
Add the parser specification class
siarhiejkresik May 5, 2019
594070d
Fix name of calling lexer method
siarhiejkresik May 5, 2019
0132c6e
Add the init file for lexer package
siarhiejkresik May 5, 2019
04da939
Refactor module members importer
siarhiejkresik May 6, 2019
1d70982
Move importer module into a separate package
siarhiejkresik May 6, 2019
19105f7
Fix package docs
siarhiejkresik May 6, 2019
1976bca
Add ERROR to parser error message and propagate exception
siarhiejkresik May 6, 2019
0f8ce5b
Update parser asserts
siarhiejkresik May 6, 2019
40702b6
Refactor import package
siarhiejkresik May 6, 2019
4f54fe8
Remove commented code
siarhiejkresik May 6, 2019
5122e2d
Refactor matchers container class
siarhiejkresik May 6, 2019
09a8e07
Add importer to calculator
siarhiejkresik May 6, 2019
1564b6e
Add matchers to calculator
siarhiejkresik May 6, 2019
981196d
Add number matcher to calculator
siarhiejkresik May 6, 2019
e3c9d25
Add parser to calculator
siarhiejkresik May 6, 2019
a81744a
Add specification to calculator
siarhiejkresik May 6, 2019
fa2c4ed
Add calculator to calculator
siarhiejkresik May 6, 2019
5438c5c
Add init file to calculator
siarhiejkresik May 6, 2019
a96d7ff
Remove unused code
siarhiejkresik May 6, 2019
9a72352
Remove unused code
siarhiejkresik May 6, 2019
8c8580b
Add docs
siarhiejkresik May 6, 2019
012ee7f
Fix imports
siarhiejkresik May 6, 2019
ea56e66
Refactor calculator package
siarhiejkresik May 6, 2019
6e01881
Add docs
siarhiejkresik May 6, 2019
f32053e
Update calculator asserts
siarhiejkresik May 6, 2019
282e433
Add constants to matchers of calculator
siarhiejkresik May 6, 2019
26de9dd
Add constants to specification of calculator
siarhiejkresik May 6, 2019
148904c
Add non equal operation to calculator
siarhiejkresik May 6, 2019
2959910
Refactor specification package
siarhiejkresik May 6, 2019
db263a2
Add logging of assert calls
siarhiejkresik May 6, 2019
26a39df
Remove unused code
siarhiejkresik May 6, 2019
e201df6
Format code
siarhiejkresik May 6, 2019
89fd3a8
Update docs
siarhiejkresik May 6, 2019
bb12b72
Fix typo
siarhiejkresik May 6, 2019
cf35b2e
Raise an exception if a source is not parsed completely
siarhiejkresik May 7, 2019
6380667
Add docs
siarhiejkresik May 7, 2019
98f6fab
Fix methods names after renaming
siarhiejkresik May 7, 2019
64cc409
Refactor matchers creating
siarhiejkresik May 7, 2019
8662e15
Update calculator specification
siarhiejkresik May 7, 2019
674c77b
Transform constant names to uppercase
siarhiejkresik May 9, 2019
e459149
Add docs
siarhiejkresik May 9, 2019
993440c
Add exceptions for the importer package
siarhiejkresik May 9, 2019
063c0b0
Raise a custom exception on module imports error
siarhiejkresik May 9, 2019
68a0f75
Add the context method to the lexer class
siarhiejkresik May 9, 2019
4e500a8
Remove unused code
siarhiejkresik May 9, 2019
9f42f68
Update docs
siarhiejkresik May 9, 2019
68a1b82
Implement exceptions for the parser package
siarhiejkresik May 9, 2019
6e02292
Add context method to the parser class
siarhiejkresik May 9, 2019
45811e3
Refactor exception raising in the parser class
siarhiejkresik May 9, 2019
20a4b57
Pass default token power to the parser constructor
siarhiejkresik May 9, 2019
a7b5d57
Implement exceptions for the specification package
siarhiejkresik May 9, 2019
b22aaea
Refactor exceptions handling in the specification package
siarhiejkresik May 9, 2019
34ebad5
Update docs
siarhiejkresik May 9, 2019
e63cf22
Add calculator's messages constants
siarhiejkresik May 9, 2019
020b08e
Add calculator's string formatters
siarhiejkresik May 9, 2019
6e23697
Remove an irrelevant todo comment
siarhiejkresik May 9, 2019
bc6bbac
Update docs
siarhiejkresik May 9, 2019
c25ca31
Implement the calculator class
siarhiejkresik May 9, 2019
e1b5681
Update calculators asserts
siarhiejkresik May 9, 2019
d405e51
Split parse method of the parse class
siarhiejkresik May 9, 2019
9199aa7
Add an exception for unregistered parselets
siarhiejkresik May 10, 2019
e77a0ae
Update docs
siarhiejkresik May 10, 2019
e5e1707
Raise an custom exception on parselet finding error
siarhiejkresik May 10, 2019
7a2d453
Refactor the led denotation class
siarhiejkresik May 10, 2019
88bb1a3
Add the parser syntax error class
siarhiejkresik May 10, 2019
ea176b5
Add docs
siarhiejkresik May 9, 2019
3606210
Add mapping exceptions to error messages
siarhiejkresik May 10, 2019
ea181fb
Update messages constants
siarhiejkresik May 10, 2019
14ea74e
Update docs
siarhiejkresik May 10, 2019
e3ca8f2
Add handling of calculation errors
siarhiejkresik May 10, 2019
906643f
Implement cli
siarhiejkresik May 10, 2019
af92b44
Refactor handling of power in the parser class
siarhiejkresik May 10, 2019
ce12efb
Update docs
siarhiejkresik May 10, 2019
3a90591
Implement parselets for calculator
siarhiejkresik May 10, 2019
2f96036
Add operator precedences
siarhiejkresik May 10, 2019
ca593c3
Implement the tokens package
siarhiejkresik May 10, 2019
aadd3af
Fix imports
siarhiejkresik May 10, 2019
2d01b3b
Add number regex object
siarhiejkresik May 10, 2019
1a30d9c
Add docs
siarhiejkresik May 6, 2019
1d8f530
Refactor matcher package
siarhiejkresik May 7, 2019
a43a92c
Remove redundant number module
siarhiejkresik May 10, 2019
7f0bff6
Rename a variable
siarhiejkresik May 10, 2019
ab0ca02
Add top-level init and main files
siarhiejkresik May 10, 2019
29715f3
Update docs
siarhiejkresik May 10, 2019
05e3e74
Code cleanup
siarhiejkresik May 10, 2019
61848bc
Merge branch 'args' into dev
siarhiejkresik May 10, 2019
e9a06fb
Merge branch 'importer' into dev
siarhiejkresik May 10, 2019
32f1217
Merge branch 'matcher' into dev
siarhiejkresik May 10, 2019
6207d55
Merge branch 'lexer' into dev
siarhiejkresik May 10, 2019
4b28491
Merge branch 'specification' into dev
siarhiejkresik May 10, 2019
43c5b86
Merge branch 'parser' into dev
siarhiejkresik May 10, 2019
d04fdd6
Merge branch 'calculator' into dev
siarhiejkresik May 10, 2019
4f8c22f
Merge branch 'cli' into dev
siarhiejkresik May 10, 2019
167a9bc
Add tests for calculation and calculation errors
siarhiejkresik Jun 3, 2019
a2e4432
Update docs
siarhiejkresik May 11, 2019
73edc6d
Refactor context getting of operation parselets
siarhiejkresik Jun 3, 2019
a2a0fbe
Convert lists to tuples
siarhiejkresik Jun 3, 2019
1d19055
Add test running into the travis config
siarhiejkresik Jun 3, 2019
1fd5bca
Refactor lexer tests and move them into a proper directory
siarhiejkresik Jun 3, 2019
0ffe2a8
Wrap parsing of command line arguments in a function
siarhiejkresik Jun 5, 2019
883f4e7
Add exceptions for calculator errors
siarhiejkresik Jun 5, 2019
11d0b01
Refactor calculator’s formatters
siarhiejkresik Jun 5, 2019
f29d325
Fix imports
siarhiejkresik Jun 5, 2019
76974cd
Refactor formatters of calculator
siarhiejkresik Jun 5, 2019
9231382
Raise exceptions on calculator errors
siarhiejkresik Jun 5, 2019
31f1f61
Refactor cli to class
siarhiejkresik Jun 5, 2019
5ee52e8
Fix the top-level main script
siarhiejkresik Jun 5, 2019
2d3276a
Remove unrelevant tests after refactoring
siarhiejkresik Jun 5, 2019
43f8e3b
Add README file
siarhiejkresik Jun 5, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@ script:
- nosetests --cover-branches --with-coverage .
- pycodestyle --max-line-length=120 .
- python ./../pycalc_checker.py
- cd -
# added
- cd final_task
- python -m unittest
- cd -
135 changes: 135 additions & 0 deletions README.md
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
34 changes: 34 additions & 0 deletions final_task/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
.DEFAULT_GOAL := run
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

С моей точки зрения в данном случае можно было обойтись без makefile.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Мне так было зручней ставіць/выдаляць праграму, правяраць, як яна працуе, запускаць pycodestyle.


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)/*
6 changes: 6 additions & 0 deletions final_task/pycalc/__init__.py
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.
"""
18 changes: 18 additions & 0 deletions final_task/pycalc/__main__.py
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()
47 changes: 47 additions & 0 deletions final_task/pycalc/args.py
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 = {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Я не вижу большого смысла в вынесении параметров для ArgumentParser в отдельные глобальные словари. Их можно было разместить прямо в аргументах функции.

'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
10 changes: 10 additions & 0 deletions final_task/pycalc/calculator/__init__.py
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
)
102 changes: 102 additions & 0 deletions final_task/pycalc/calculator/calculator.py
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):
Copy link
Collaborator

Choose a reason for hiding this comment

The 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)
Loading