Skip to content

Commit 672abbd

Browse files
committed
Version 0.3
1 parent b29a630 commit 672abbd

File tree

6 files changed

+56
-23
lines changed

6 files changed

+56
-23
lines changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,3 +155,16 @@ for boosts on both performance and precision.
155155
- Many functions now have positional-only arguments for slight performance boosts
156156
- This drops support for Python 3.7
157157
- Messages retrieved from `ParsingError.friendly` are now much more descriptive.
158+
159+
### v0.3
160+
161+
#### What's new?
162+
- Unary plus is now supported (E.g. `+5`)
163+
- Scientific notation is now supported (E.g. `4E-2`)
164+
- To reduce conflics, 'E' __must__ be captialized.
165+
This means that `2e9` would evaluate to `2 * e * 9`, for example.
166+
- The `cls` kwarg is now supported in `expr.evaluate`
167+
168+
#### Bug fixes
169+
- Catch `OverflowError` in the `expr.Overflow` parsing error.
170+
- Fix invalid typings with `Callable`

expr/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@
88
from .grammar import *
99
from .parser import *
1010

11-
__version__ = '0.2.2'
11+
__version__ = '0.3.0'
1212
__author__ = 'jay3332'

expr/core.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1-
from typing import Optional, Tuple
1+
from decimal import Decimal
2+
from typing import Optional, Tuple, Type, TypeVar, Union
3+
24
from .parser import Parser
35

6+
7+
C = TypeVar('C', bound=Union[float, Decimal])
8+
9+
410
__all__: Tuple[str, ...] = (
511
'evaluate',
612
'create_state',
@@ -10,13 +16,13 @@
1016
state: Optional[Parser] = None
1117

1218

13-
def evaluate(expr: str, /, **kwargs) -> float:
19+
def evaluate(expr: str, /, *, cls: Type[C] = Decimal, **kwargs) -> C:
1420
global state
1521

1622
if not state:
1723
state = Parser(**kwargs)
1824

19-
return state.evaluate(expr)
25+
return state.evaluate(expr, cls=cls)
2026

2127

2228
def create_state(**kwargs) -> Parser:

expr/grammar.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ def add_ignores(self, /) -> None:
3030

3131
def add_basic(self, /) -> None:
3232
self.add('NUMBER', r'([0-9]+(\.[0-9]*)?|\.[0-9]+)')
33+
self.add('E', 'E')
3334
self.add('NAME', r'[a-zA-Z_][a-zA-Z0-9_]*')
3435
self.add('LPAREN', r'\(')
3536
self.add('RPAREN', r'\)')

expr/parser.py

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -86,25 +86,25 @@ def __new_init__(self, *args, **kwargs):
8686
# noinspection PyArgumentList,PyUnresolvedReferences
8787
class Parser(metaclass=ParserMeta):
8888
def __init__(
89-
self,
90-
/,
91-
*,
92-
max_safe_number: float = 9e9,
93-
max_exponent: float = 128,
94-
max_factorial: float = 64,
95-
builtins: Dict[str, Callable[[DT], DT]] = None,
96-
constants: Dict[str, DT] = None,
97-
variables: Dict[str, DT] = None,
98-
decimal_cls: Type[DT] = Decimal,
99-
lexer_cls: Type[LGT] = LexerGenerator
89+
self,
90+
/,
91+
*,
92+
max_safe_number: float = 9e9,
93+
max_exponent: float = 128,
94+
max_factorial: float = 64,
95+
builtins: Dict[str, Callable[[DT], DT]] = None,
96+
constants: Dict[str, DT] = None,
97+
variables: Dict[str, DT] = None,
98+
decimal_cls: Type[DT] = Decimal,
99+
lexer_cls: Type[LGT] = LexerGenerator
100100
) -> None:
101101
if not issubclass(decimal_cls, Decimal):
102102
raise TypeError('decimal_cls must inherit from decimal.Decimal')
103103

104104
_ = decimal_cls
105-
self._max_safe_number: DT = _(max_safe_number)
106-
self._max_exponent: DT = _(max_exponent)
107-
self._max_factorial: DT = _(max_factorial)
105+
self._max_safe_number: DT = _(max_safe_number) if max_safe_number is not None else _('inf')
106+
self._max_exponent: DT = _(max_exponent) if max_exponent is not None else _('inf')
107+
self._max_factorial: DT = _(max_factorial) if max_factorial is not None else _('inf')
108108

109109
_builtins: Dict[str, Callable[[DT], DT]] = {
110110
'rad': lambda d: _(math.radians(float(d))),
@@ -155,9 +155,6 @@ def __init__(
155155

156156
self._functions: Dict[str, Callable[[DT], DT]] = _builtins
157157

158-
def __repr__(self) -> str:
159-
return f'<expr.{self.__class__.__name__}>'
160-
161158
@rule('expr : NUMBER')
162159
def number(self, p: List[_Token], /) -> Number:
163160
number = Number(p[0].getstr())
@@ -208,6 +205,10 @@ def operator(self, p: List[_Token], /) -> Any:
208205
def uminus(self, p: List[_Token], /) -> Any:
209206
return Number(-(p[1].eval()))
210207

208+
@rule("expr : ADD expr", precedence='UMINUS')
209+
def upos(self, p: List[_Token], /) -> Any:
210+
return p[1]
211+
211212
@rule('expr : NAME EQ expr')
212213
def declare(self, p: List[_Token], /) -> Any:
213214
_name = p[0].getstr()
@@ -223,6 +224,10 @@ def function(self, p: List[_Token], /) -> Any:
223224

224225
return Mul(self.getvar(p[0]), p[2]) # Probably this instead
225226

227+
@rule("expr : expr E expr", precedence='POW')
228+
def scinot_e(self, p: List[_Token], /) -> Any:
229+
return Mul(p[0], Pow(Number(10), p[2]))
230+
226231
@rule('expr : NAME')
227232
def getvar(self, p: List[_Token], /) -> Any:
228233
name = p[0].getstr()
@@ -248,6 +253,9 @@ def _build_lexer(self, /) -> Lexer:
248253
self.__lexer__ = res = self.__lexer_generator__.build()
249254
return res
250255

256+
def __repr__(self, /) -> str:
257+
return f'<expr.{self.__class__.__name__} object at {id(self):x}>'
258+
251259
def __call__(self, expr: str, /, *, cls: Type[OT] = Decimal) -> Optional[OT]:
252260
return self.evaluate(expr, cls=cls)
253261

@@ -265,6 +273,9 @@ def evaluate(self, expr: str, /, *, cls: Type[OT] = Decimal) -> Optional[OT]:
265273
except (ZeroDivisionError, _ZeroDivision):
266274
raise DivisionByZero()
267275

276+
except (ValueError, OverflowError):
277+
raise Overflow()
278+
268279
except InvalidOperation as exc:
269280
if isinstance(exc.args[0][0], DivisionUndefined):
270281
raise DivisionByZero()

setup.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,19 @@
33

44

55
with open('expr/__init__.py') as f:
6+
r = f.read()
7+
68
try:
79
version = re.search(
8-
r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', f.read(), re.M
10+
r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', r, re.M
911
).group(1)
1012
except AttributeError:
1113
raise RuntimeError('Could not identify version') from None
1214

1315
# look at this boilerplate code
1416
try:
1517
author = re.search(
16-
r'^__author__\s*=\s*[\'"]([^\'"]*)[\'"]', f.read(), re.M
18+
r'^__author__\s*=\s*[\'"]([^\'"]*)[\'"]', r, re.M
1719
).group(1)
1820
except AttributeError:
1921
author = 'jay3332'

0 commit comments

Comments
 (0)