1 """
2 Safe(ish) evaluator of python expressions, using ast module.
3 The emphasis here is on mathematical expressions, and so
4 numpy functions are imported if available and used.
5
6 Symbols are held in the Interpreter symtable -- a simple
7 dictionary supporting a simple, flat namespace.
8
9 Expressions can be compiled into ast node and then evaluated
10 later, using the current values in the
11 """
12 from __future__ import division, print_function
13 from sys import exc_info, stdout, version_info
14 import ast
15 import math
16 from .astutils import (FROM_PY, FROM_MATH, FROM_NUMPY,
17 NUMPY_RENAMES, ExceptionHolder)
18
19 from .parameter import isParameter, valid_symbol_name
20
21 HAS_NUMPY = False
22 try:
23 import numpy
24 HAS_NUMPY = True
25 except ImportError:
26 print("Warning: numpy not available... functionality will be limited.")
27
28
29 OPERATORS = {
30 ast.Add: lambda a, b: b.__radd__(a) if isParameter(b) else a + b,
31 ast.Sub: lambda a, b: b.__rsub__(a) if isParameter(b) else a - b,
32 ast.Mult: lambda a, b: b.__rmul__(a) if isParameter(b) else a * b,
33 ast.Div: lambda a, b: b.__rdiv__(a) if isParameter(b) else a / b,
34 ast.FloorDiv: lambda a, b: b.__rfloordiv__(a) if isParameter(b) else a // b,
35 ast.Mod: lambda a, b: b.__rmod__(a) if isParameter(b) else a % b,
36 ast.Pow: lambda a, b: b.__rpow__(a) if isParameter(b) else a ** b,
37 ast.Eq: lambda a, b: b.__eq__(a) if isParameter(b) else a == b,
38 ast.Gt: lambda a, b: b.__le__(a) if isParameter(b) else a > b,
39 ast.GtE: lambda a, b: b.__lt__(a) if isParameter(b) else a >= b,
40 ast.Lt: lambda a, b: b.__ge__(a) if isParameter(b) else a < b,
41 ast.LtE: lambda a, b: b.__gt__(a) if isParameter(b) else a <= b,
42 ast.NotEq: lambda a, b: b.__ne__(a) if isParameter(b) else a != b,
43 ast.Is: lambda a, b: a is b,
44 ast.IsNot: lambda a, b: a is not b,
45 ast.In: lambda a, b: a in b,
46 ast.NotIn: lambda a, b: a not in b,
47 ast.BitAnd: lambda a, b: a & b,
48 ast.BitOr: lambda a, b: a | b,
49 ast.BitXor: lambda a, b: a ^ b,
50 ast.LShift: lambda a, b: a << b,
51 ast.RShift: lambda a, b: a >> b,
52 ast.And: lambda a, b: a and b,
53 ast.Or: lambda a, b: a or b,
54 ast.Invert: lambda a: ~a,
55 ast.Not: lambda a: not a,
56 ast.UAdd: lambda a: +a,
57 ast.USub: lambda a: -a}
58
60 "return function for operator nodes"
61 return OPERATORS[op.__class__]
62
63 __version__ = '0.3.1'
64
65
67 """basic empty containter"""
70
71 ReturnedNone = Empty()
72
74 """mathematical expression compiler and interpreter.
75
76 This module compiles expressions and statements to AST representation,
77 using python's ast module, and then executes the AST representation
78 using a dictionary of named object (variable, functions).
79
80 This then gives a restricted version of Python, being a procedural
81 language (though working on Python objects) with a simplified, flat
82 namespace (this is overcome in related implementaions). The program
83 syntax here is expected to be valid Python.
84
85 The following Python syntax elements are not supported:
86 Import, Exec, Lambda, Class, Global, Generators,
87 Yield, Decorators, Finally for Try-Except
88
89 Many parts of Python syntax are supported, including:
90 advanced slicing: a[::-1], array[-3:, :, ::2]
91 if-expressions: out = one_thing if TEST else other
92 list comprehension
93 for-loops, while-loops
94 if-then-elif-else conditionals
95 try-except (but not the 'finally' variant...)
96 function definitions with def
97 """
98
99 supported_nodes = ('arg', 'assert', 'assign', 'attribute', 'augassign',
100 'binop', 'boolop', 'break', 'call', 'compare',
101 'continue', 'delete', 'dict', 'ellipsis',
102 'excepthandler', 'expr', 'extslice', 'for',
103 'functiondef', 'if', 'ifexp', 'index', 'interrupt',
104 'list', 'listcomp', 'module', 'name', 'num', 'pass',
105 'print', 'raise', 'repr', 'return', 'slice', 'str',
106 'subscript', 'tryexcept', 'tuple', 'unaryop',
107 'while')
108
109 - def __init__(self, symtable=None, writer=None, use_numpy=True):
110 self.writer = writer or stdout
111
112 if symtable is None:
113 symtable = {}
114 self.symtable = symtable
115 self._interrupt = None
116 self.error = []
117 self.expr = None
118 self.retval = None
119 self.errmsg = None
120 self.lineno = 0
121 global HAS_NUMPY
122 if not use_numpy:
123 HAS_NUMPY = False
124
125 symtable['print'] = self._printer
126 for sym in FROM_PY:
127 if sym in __builtins__:
128 symtable[sym] = __builtins__[sym]
129 for sym in FROM_MATH:
130 if hasattr(math, sym):
131 symtable[sym] = getattr(math, sym)
132
133 if HAS_NUMPY:
134 for sym in FROM_NUMPY:
135 if hasattr(numpy, sym):
136 symtable[sym] = getattr(numpy, sym)
137 for name, sym in NUMPY_RENAMES.items():
138 if hasattr(numpy, sym):
139 symtable[name] = getattr(numpy, sym)
140
141 self.node_handlers = dict(((node, getattr(self, "on_%s" % node))
142 for node in self.supported_nodes))
143
145 "unimplemented nodes"
146 self.raise_exception(node, exc=NotImplementedError,
147 msg="'%s' not supported" % (node.__class__.__name__))
148
149 - def raise_exception(self, node, exc=None, msg='', expr=None,
150 lineno=None):
151 "add an exception"
152 if self.error is None:
153 self.error = []
154 if expr is None:
155 expr = self.expr
156
157 if len(self.error) > 0 and not isinstance(node, ast.Module):
158 msg = '%s' % msg
159 if self.errmsg is None:
160 self.errmsg = msg
161 err = ExceptionHolder(node, exc=exc, msg=self.errmsg,
162 expr=expr, lineno=lineno)
163 self._interrupt = ast.Break()
164 self.error.append(err)
165 raise RuntimeError(err.msg)
166
167
168
169
170
172 """parse statement/expression to Ast representation"""
173 self.expr = text
174 try:
175 return ast.parse(text)
176 except:
177 self.raise_exception(None, exc=SyntaxError,
178 msg='Syntax Error', expr=text)
179
180 - def run(self, node, expr=None, lineno=None, with_raise=True):
181 """executes parsed Ast representation for an expression"""
182
183
184 if len(self.error)>0:
185 return
186 if node is None:
187 return None
188 if isinstance(node, str):
189 node = self.parse(node)
190 if lineno is not None:
191 self.lineno = lineno
192
193 if expr is not None:
194 self.expr = expr
195
196
197
198 try:
199 handler = self.node_handlers[node.__class__.__name__.lower()]
200 except KeyError:
201 return self.unimplemented(node)
202
203
204
205 try:
206 ret = handler(node)
207 if isinstance(ret, enumerate):
208 ret = list(ret)
209 return ret
210 except:
211 if with_raise:
212 self.raise_exception(node, expr=expr)
213
215 return self.eval(expr, **kw)
216
217 - def eval(self, expr, lineno=0, show_errors=True):
218 """evaluates a single statement"""
219 self.lineno = lineno
220 self.errmsg = None
221 self.error = []
222 try:
223 node = self.parse(expr)
224 except RuntimeError:
225 errmsg = exc_info()[1]
226 if len(self.error) > 0:
227 errmsg = "\n".join(self.error[0].get_error())
228 if not show_errors:
229 raise RuntimeError(errmsg)
230 print(errmsg, file=self.writer)
231 return
232 try:
233 return self.run(node, expr=expr, lineno=lineno)
234 except RuntimeError:
235 errmsg = exc_info()[1]
236 if len(self.error) > 0:
237 errmsg = "\n".join(self.error[0].get_error())
238 if not show_errors:
239 raise RuntimeError(errmsg)
240 print(errmsg, file=self.writer)
241 return
242
243 - def dump(self, node, **kw):
244 "simple ast dumper"
245 return ast.dump(node, **kw)
246
247
249 "expression"
250 return self.run(node.value)
251
253 "index"
254 return self.run(node.value)
255
257 "return statement: look for None, return special sentinal"
258 self.retval = self.run(node.value)
259 if self.retval is None:
260 self.retval = ReturnedNone
261 return
262
264 "repr "
265 return repr(self.run(node.value))
266
268 "module def"
269 out = None
270 for tnode in node.body:
271 out = self.run(tnode)
272 return out
273
275 "pass statement"
276 return None
277
279 "ellipses"
280 return Ellipsis
281
282
284 "interrupt handler"
285 self._interrupt = node
286 return node
287
291
295
297 "assert statement"
298 if not self.run(node.test):
299 self.raise_exception(node, exc=AssertionError, msg=node.msg)
300 return True
301
303 "list"
304 return [self.run(e) for e in node.elts]
305
307 "tuple"
308 return tuple(self.on_list(node))
309
311 "dictionary"
312 return dict([(self.run(k), self.run(v)) for k, v in \
313 zip(node.keys, node.values)])
314
316 'return number'
317 return node.n
318
320 'return string'
321 return node.s
322
324 """ Name node """
325 ctx = node.ctx.__class__
326 if ctx in (ast.Param, ast.Del):
327 return str(node.id)
328 else:
329 if node.id in self.symtable:
330 return self.symtable[node.id]
331 else:
332 msg = "name '%s' is not defined" % node.id
333 self.raise_exception(node, exc=NameError, msg=msg)
334
335
337 """here we assign a value (not the node.value object) to a node
338 this is used by on_assign, but also by for, list comprehension, etc.
339 """
340 if node.__class__ == ast.Name:
341 if not valid_symbol_name(node.id):
342 errmsg = "invalid symbol name (reserved word?) %s" % node.id
343 self.raise_exception(node, exc=NameError, msg=errmsg)
344 sym = self.symtable[node.id] = val
345 elif node.__class__ == ast.Attribute:
346 if node.ctx.__class__ == ast.Load:
347 msg = "cannot assign to attribute %s" % node.attr
348 self.raise_exception(node, exc=AttributeError, msg=msg)
349
350 setattr(self.run(node.value), node.attr, val)
351
352 elif node.__class__ == ast.Subscript:
353 sym = self.run(node.value)
354 xslice = self.run(node.slice)
355 if isinstance(node.slice, ast.Index):
356 sym[xslice] = val
357 elif isinstance(node.slice, ast.Slice):
358 sym[slice(xslice.start, xslice.stop)] = val
359 elif isinstance(node.slice, ast.ExtSlice):
360 sym[(xslice)] = val
361 elif node.__class__ in (ast.Tuple, ast.List):
362 if len(val) == len(node.elts):
363 for telem, tval in zip(node.elts, val):
364 self.node_assign(telem, tval)
365 else:
366 raise ValueError('too many values to unpack')
367
369 "extract attribute"
370 ctx = node.ctx.__class__
371 if ctx == ast.Load:
372 sym = self.run(node.value)
373 if hasattr(sym, node.attr):
374 return getattr(sym, node.attr)
375 else:
376 obj = self.run(node.value)
377 fmt = "%s does not have attribute '%s'"
378 msg = fmt % (obj, node.attr)
379 self.raise_exception(node, exc=AttributeError, msg=msg)
380
381 elif ctx == ast.Del:
382 return delattr(sym, node.attr)
383 elif ctx == ast.Store:
384 msg = "attribute for storage: shouldn't be here!"
385 self.raise_exception(node, exc=RuntimeError, msg=msg)
386
388 "simple assignment"
389 val = self.run(node.value)
390 for tnode in node.targets:
391 self.node_assign(tnode, val)
392 return
393
395 "augmented assign"
396 return self.on_assign(ast.Assign(targets=[node.target],
397 value=ast.BinOp(left = node.target,
398 op = node.op,
399 right= node.value)))
400
402 "simple slice"
403 return slice(self.run(node.lower),
404 self.run(node.upper),
405 self.run(node.step))
406
407
409 "extended slice"
410 return tuple([self.run(tnode) for tnode in node.dims])
411
413 "subscript handling -- one of the tricky parts"
414 val = self.run(node.value)
415 nslice = self.run(node.slice)
416 ctx = node.ctx.__class__
417 if ctx in ( ast.Load, ast.Store):
418 if isinstance(node.slice, (ast.Index, ast.Slice, ast.Ellipsis)):
419 return val.__getitem__(nslice)
420 elif isinstance(node.slice, ast.ExtSlice):
421 return val[(nslice)]
422 else:
423 msg = "subscript with unknown context"
424 self.raise_exception(node, msg=msg)
425
427 "delete statement"
428 for tnode in node.targets:
429 if tnode.ctx.__class__ != ast.Del:
430 break
431 children = []
432 while tnode.__class__ == ast.Attribute:
433 children.append(tnode.attr)
434 tnode = tnode.value
435
436 if tnode.__class__ == ast.Name:
437 children.append(tnode.id)
438 children.reverse()
439 self.symtable.pop('.'.join(children))
440 else:
441 msg = "could not delete symbol"
442 self.raise_exception(node, msg=msg)
443
445 "unary operator"
446 return op2func(node.op)(self.run(node.operand))
447
449 "binary operator"
450 return op2func(node.op)(self.run(node.left),
451 self.run(node.right))
452
454 "boolean operator"
455 val = self.run(node.values[0])
456 is_and = ast.And == node.op.__class__
457 if (is_and and val) or (not is_and and not val):
458 for n in node.values:
459 val = op2func(node.op)(val, self.run(n))
460 if (is_and and not val) or (not is_and and val):
461 break
462 return val
463
465 "comparison operators"
466 lval = self.run(node.left)
467 out = True
468 for op, rnode in zip(node.ops, node.comparators):
469 rval = self.run(rnode)
470 out = op2func(op)(lval, rval)
471 lval = rval
472 if HAS_NUMPY and isinstance(out, numpy.ndarray) and out.any():
473 break
474 elif not out:
475 break
476 return out
477
479 """ note: implements Python2 style print statement, not
480 print() function. May need improvement...."""
481 dest = self.run(node.dest) or self.writer
482 end = ''
483 if node.nl:
484 end = '\n'
485 out = [self.run(tnode) for tnode in node.values]
486 if out and len(self.error)==0:
487 self._printer(*out, file=dest, end=end)
488
490 "generic print function"
491 flush = kws.pop('flush', True)
492 fileh = kws.pop('file', self.writer)
493 sep = kws.pop('sep', ' ')
494 end = kws.pop('sep', '\n')
495
496 print(*out, file=fileh, sep=sep, end=end)
497 if flush:
498 fileh.flush()
499
501 "regular if-then-else statement"
502 block = node.body
503 if not self.run(node.test):
504 block = node.orelse
505 for tnode in block:
506 self.run(tnode)
507
509 "if expressions"
510 expr = node.orelse
511 if self.run(node.test):
512 expr = node.body
513 return self.run(expr)
514
516 "while blocks"
517 while self.run(node.test):
518 self._interrupt = None
519 for tnode in node.body:
520 self.run(tnode)
521 if self._interrupt is not None:
522 break
523 if isinstance(self._interrupt, ast.Break):
524 break
525 else:
526 for tnode in node.orelse:
527 self.run(tnode)
528 self._interrupt = None
529
531 "for blocks"
532 for val in self.run(node.iter):
533 self.node_assign(node.target, val)
534 self._interrupt = None
535 for tnode in node.body:
536 self.run(tnode)
537 if self._interrupt is not None:
538 break
539 if isinstance(self._interrupt, ast.Break):
540 break
541 else:
542 for tnode in node.orelse:
543 self.run(tnode)
544 self._interrupt = None
545
547 "list comprehension"
548 out = []
549 for tnode in node.generators:
550 if tnode.__class__ == ast.comprehension:
551 for val in self.run(tnode.iter):
552 self.node_assign(tnode.target, val)
553 add = True
554 for cond in tnode.ifs:
555 add = add and self.run(cond)
556 if add:
557 out.append(self.run(node.elt))
558 return out
559
561 "exception handler..."
562 return (self.run(node.type), node.name, node.body)
563
565 "try/except blocks"
566 no_errors = True
567 for tnode in node.body:
568 self.run(tnode, with_raise=False)
569 no_errors = no_errors and len(self.error) == 0
570 if len(self.error) > 0:
571 e_type, e_value, e_tback = self.error[-1].exc_info
572 for hnd in node.handlers:
573 htype = None
574 if hnd.type is not None:
575 htype = __builtins__.get(hnd.type.id, None)
576 if htype is None or isinstance(e_type(), htype):
577 self.error = []
578 if hnd.name is not None:
579 self.node_assign(hnd.name, e_value)
580 for tline in hnd.body:
581 self.run(tline)
582 break
583 if no_errors:
584 for tnode in node.orelse:
585 self.run(tnode)
586
588 "raise statement: note difference for python 2 and 3"
589 if version_info[0] == 3:
590 excnode = node.exc
591 msgnode = node.cause
592 else:
593 excnode = node.type
594 msgnode = node.inst
595 out = self.run(excnode)
596 msg = ' '.join(out.args)
597 msg2 = self.run(msgnode)
598 if msg2 not in (None, 'None'):
599 msg = "%s: %s" % (msg, msg2)
600 self.raise_exception(None, exc=out.__class__, msg=msg, expr='')
601
603 "function execution"
604
605 func = self.run(node.func)
606 if not hasattr(func, '__call__') and not isinstance(func, type):
607 msg = "'%s' is not callable!!" % (func)
608 self.raise_exception(node, exc=TypeError, msg=msg)
609
610 args = [self.run(targ) for targ in node.args]
611 if node.starargs is not None:
612 args = args + self.run(node.starargs)
613
614 keywords = {}
615 for key in node.keywords:
616 if not isinstance(key, ast.keyword):
617 msg = "keyword error in function call '%s'" % (func)
618 self.raise_exception(node, msg=msg)
619
620 keywords[key.arg] = self.run(key.value)
621 if node.kwargs is not None:
622 keywords.update(self.run(node.kwargs))
623
624 try:
625 return func(*args, **keywords)
626 except:
627 self.raise_exception(node, exc=RuntimeError,
628 msg = "Error running %s" % (func))
629
631 "arg for function definitions"
632
633 return node.arg
634
636 "define procedures"
637
638 if node.decorator_list != []:
639 raise Warning("decorated procedures not supported!")
640 kwargs = []
641
642 offset = len(node.args.args) - len(node.args.defaults)
643 for idef, defnode in enumerate(node.args.defaults):
644 defval = self.run(defnode)
645 keyval = self.run(node.args.args[idef+offset])
646 kwargs.append((keyval, defval))
647
648 if version_info[0] == 3:
649 args = [tnode.arg for tnode in node.args.args[:offset]]
650 else:
651 args = [tnode.id for tnode in node.args.args[:offset]]
652
653 doc = None
654 if (isinstance(node.body[0], ast.Expr) and
655 isinstance(node.body[0].value, ast.Str)):
656 docnode = node.body[0]
657 doc = docnode.value.s
658
659 self.symtable[node.name] = Procedure(node.name, self, doc=doc,
660 body = node.body,
661 lineno = self.lineno,
662 args = args,
663 kwargs = kwargs,
664 vararg = node.args.vararg,
665 varkws = node.args.kwarg)
666
668 """Procedure: user-defined function for asteval
669
670 This stores the parsed ast nodes as from the
671 'functiondef' ast node for later evaluation.
672 """
673 - def __init__(self, name, interp, doc=None, lineno=0,
674 body=None, args=None, kwargs=None,
675 vararg=None, varkws=None):
676 self.name = name
677 self.interpreter = interp
678 self.raise_exc = self.interpreter.raise_exception
679 self.__doc__ = doc
680 self.body = body
681 self.argnames = args
682 self.kwargs = kwargs
683 self.vararg = vararg
684 self.varkws = varkws
685 self.lineno = lineno
686
688 sig = ""
689 if len(self.argnames) > 0:
690 sig = "%s%s" % (sig, ', '.join(self.argnames))
691 if self.vararg is not None:
692 sig = "%s, *%s" % (sig, self.vararg)
693 if len(self.kwargs) > 0:
694 if len(sig) > 0:
695 sig = "%s, " % sig
696 _kw = ["%s=%s" % (k, v) for k, v in self.kwargs]
697 sig = "%s%s" % (sig, ', '.join(_kw))
698
699 if self.varkws is not None:
700 sig = "%s, **%s" % (sig, self.varkws)
701 sig = "<Procedure %s(%s)>" % (self.name, sig)
702 if self.__doc__ is not None:
703 sig = "%s\n %s" % (sig, self.__doc__)
704 return sig
705
707 symlocals = {}
708 args = list(args)
709 n_args = len(args)
710 n_names = len(self.argnames)
711 n_kws = len(kwargs)
712
713
714 if (n_args < n_names) and n_kws > 0:
715 for name in self.argnames[n_args:]:
716 if name in kwargs:
717 args.append(kwargs.pop(name))
718 n_args = len(args)
719 n_names = len(self.argnames)
720 n_kws = len(kwargs)
721
722 if len(self.argnames) > 0 and kwargs is not None:
723 msg = "multiple values for keyword argument '%s' in Procedure %s"
724 for targ in self.argnames:
725 if targ in kwargs:
726 self.raise_exc(None, exc=TypeError,
727 msg=msg % (targ, self.name),
728 lineno=self.lineno)
729
730 if n_args != n_names:
731 msg = None
732 if n_args < n_names:
733 msg = 'not enough arguments for Procedure %s()' % self.name
734 msg = '%s (expected %i, got %i)'% (msg, n_names, n_args)
735 self.raise_exc(None, exc=TypeError, msg=msg)
736
737
738 for argname in self.argnames:
739 symlocals[argname] = args.pop(0)
740
741 try:
742 if self.vararg is not None:
743 symlocals[self.vararg] = tuple(args)
744
745 for key, val in self.kwargs:
746 if key in kwargs:
747 val = kwargs.pop(key)
748 symlocals[key] = val
749
750 if self.varkws is not None:
751 symlocals[self.varkws] = kwargs
752
753 elif len(kwargs) > 0:
754 msg = 'extra keyword arguments for Procedure %s (%s)'
755 msg = msg % (self.name, ','.join(list(kwargs.keys())))
756 self.raise_exc(None, msg=msg, exc=TypeError,
757 lineno=self.lineno)
758
759 except (ValueError, LookupError, TypeError,
760 NameError, AttributeError):
761 msg = 'incorrect arguments for Procedure %s' % self.name
762 self.raise_exc(None, msg=msg, lineno=self.lineno)
763
764 save_symtable = self.interpreter.symtable.copy()
765 self.interpreter.symtable.update(symlocals)
766 self.interpreter.retval = None
767 retval = None
768
769
770 for node in self.body:
771 self.interpreter.run(node, expr='<>', lineno=self.lineno)
772 if len(self.interpreter.error) > 0:
773 break
774 if self.interpreter.retval is not None:
775 retval = self.interpreter.retval
776 if retval is ReturnedNone: retval = None
777 break
778
779 self.interpreter.symtable = save_symtable
780 symlocals = None
781 return retval
782