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

Conversation

siarhiejkresik
Copy link

@siarhiejkresik siarhiejkresik commented May 10, 2019

Description

A command-line calculator implemented using Top Down Operator Precedence parsing algorithm (Pratt parser).

Minimal requirements

  • Calculator should be a command-line utility which receives mathematical
    expression string as an argument and prints evaluated result:
$ pycalc '2+2*2'
6
  • It should provide the following interface:
$ pycalc --help
usage: pycalc [-h] EXPRESSION

Pure-python command-line calculator.

positional arguments:
  EXPRESSION            expression string to evaluate
  • In case of any mistakes in the expression utility should print human-readable
    error explanation with "ERROR: " prefix and exit with non-zero exit code:
$ 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 жыве calendar беларусь time
ERROR: no module(s) named жыве, беларусь
$ pycalc '2+3'
5
$ echo $?
0

$ pycalc '2+'
ERROR: syntax error
2+
  ^
$ echo $?
1

Mathematical operations calculator must support

  • Arithmetic (+, -, *, /, //, %, ^) (^ is a power).
  • Comparison (<, <=, ==, !=, >=, >).
  • 2 built-in python functions: abs and round.
  • All functions and constants from standard python module math (trigonometry, logarithms, etc.).
$ pycalc 'e + pi + tau'
12.143059789228424


$ pycalc '1 + inf'
inf

$ pycalc '1 - inf'
-inf

$ pycalc 'inf - inf'
nan

$ pycalc 'nan == nan'
False

Non-functional requirements

  • It is mandatory to use argparse module.
  • Codebase must be covered with unit tests with at least 70% coverage.
  • Usage of external modules is prohibited (python standard library only).
  • Usage of eval and exec is prohibited.
  • Usage of ast and tokenize modules is prohibited.

Distribution

  • Utility should be wrapped into distribution package with setuptools.
  • This package should export CLI utility named pycalc.

Codestyle

  • Docstrings are mandatory for all methods, classes, functions and modules.
  • Code must correspond to pep8 (use pycodestyle utility for self-check).
  • You can set line length up to 120 symbols.

Optional requirements

These requirement is not mandatory for implementation, but you can get more points for it.

  • Support of functions and constants from the modules provided with -m or --use-modules command-line option.
    This option should accept names of the modules which are accessible via
    python standard module search paths.
    Functions and constants from user defined modules have higher priority in case of name conflict then stuff from math module or built-in functions. (bug: -m <module_name> won’t override built-in or standard <module_name>)

    In this case pycalc utility should provide the following interface:

    $ 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

    Examples:

    # my_module.py
    def sin(number):
        return 42
    $ 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

@siarhiejkresik siarhiejkresik changed the title [WIP] Siarhiej Kresik Siarhiej Kresik Jun 5, 2019
@@ -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.

}
}

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 в отдельные глобальные словари. Их можно было разместить прямо в аргументах функции.

def is_numeric(obj) -> bool:
"""Return `True` if a object is one of numeric types."""

return isinstance(obj, (NUMERIC_TYPES))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Для чего NUMERIC_TYPES обернут в скобочки?

Copy link
Author

Choose a reason for hiding this comment

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

Яны там лішнія. Засталіся, відаць, пасля таго, як вынес tuple ў канстанту.

from .punctuation import Comma


__all__ = [
Copy link
Collaborator

Choose a reason for hiding this comment

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

__all__ содержит все значения, которые есть в этом модуле. То есть в целом нет смысла определения этой переменной

self.const_registry = const_registry

def nud(self, parser, token):
""""""
Copy link
Collaborator

Choose a reason for hiding this comment

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

Пустой докстринг

Copy link
Author

Choose a reason for hiding this comment

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

Я так і не прыдумаў, як гэтыя штукі абазваць :D


try:
self.calculator = calculator(modules)
return
Copy link
Collaborator

Choose a reason for hiding this comment

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

В данном случае return можно опустить

except Exception as exc:
err_msg = str(exc)

self.on_error(err_msg)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Очень странный подход, который сильно запутывает.
В данном случае получается, что последняя строчка функции исполняется только в случае если было отловлено исключение, а при нормальном исполнении функции до этой строчки исполнение не дойдет.

Это мелочь, но лично для меня это смотрится странно

Copy link
Author

Choose a reason for hiding this comment

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

Але па-другому б у канцы кожнага except прыйшлося б пісаць self.on_error(err_msg).


self.on_error(err_msg)

def on_success(self, result):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Я думаю, что такие однострочные методы только усложняют код.
В данном случае этот метод вызывается только в одном месте и все, что он делает, это проксирует вызов другому методу. То есть фактически он ничего не делает.

Copy link
Author

Choose a reason for hiding this comment

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

З аднаго боку, яно, вядома ж, і так. Але скажам, заўтра вам скажуць, што калькулятр мае пісаць логі і пасылаць рэзультат/памылку на сервер. І калі для памылкі ёсць куды засунуць логіку, то для рэзультата трэба будзе разбірацца ў кодзе і думаць, куды яе напісаць.


sys.exit()

def prefix_err_msg(self, msg):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Аналогичная ситуация. Метод вызывается только в одном месте и делает только конкатенацию двух строк.

Copy link
Author

Choose a reason for hiding this comment

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

Функцыя прэфіксатар робіць роўна тое, што і трэба — дадае прэфік ERROR:.
Лёгка тэставаць, лёгка мяняць, адразу відаць, што робіць, не мяшаецца з другім кодам.
Другая рэч, што можа ёй ня месца ў класе, але гэта заўсёды праблема — куды засунуць падобную функцыю, калі яна адна-дзве такія.

UNDERSCORE = '_'


def iter_uniq(iterables):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Эту же функциональность делает set -- хранит уникальные значения.

import re


def list_sorted_by_length(iterable) -> list:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Функция вызывается только в одном месте и делает ровно одно действите -- сортировку с помощью стандартной функции. Можно было обойтись прямым вызовом sorted

return sorted(iterable, key=len, reverse=True)


def construct_literals_list(literals):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Такая же ситуация


@unittest.skip('not implemented')
def test_peek(self):
pass
Copy link
Collaborator

Choose a reason for hiding this comment

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

not implemented

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.

Нэйминг немного запутывает. В данном модуле есть класс калькулятор и функция калькулятор

@AlexeiBuzuma
Copy link
Collaborator

В основном код достаточно чистый и легко читаемый со стилистической точки зрения, однако я вижу тут одну комплексную проблему.
Во многих местах есть лишний код. Например есть большое количество функций/методов, которые просто проксируют вызов в другие функции.
Есть исключения, которые используются только в одном месте (можно было просто сделать более общее исключение для группы исключений и передавать разный текст ошибки.) Есть большое количество переменных, создание которых можно было легко избежать.

Код ощущается перенагруженным, большая его часть можно сказать ничего не делает.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants