Skip to content

Commit b093ced

Browse files
Single-source Py2/3 simple_eval
1 parent d36c30d commit b093ced

File tree

2 files changed

+45
-14
lines changed

2 files changed

+45
-14
lines changed

bpython/simpleeval.py

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
import ast
99
from six import string_types
1010
from six.moves import builtins
11+
from numbers import Number
1112

1213
from bpython import line as line_properties
14+
from bpython._py3compat import py3
1315

1416
class EvaluationError(Exception):
1517
"""Raised if an exception occurred in safe_eval."""
@@ -24,7 +26,7 @@ def safe_eval(expr, namespace):
2426
# raise
2527
raise EvaluationError
2628

27-
# taken from Python 2 stdlib ast.literal_eval
29+
2830
def simple_eval(node_or_string, namespace=None):
2931
"""
3032
Safely evaluate an expression node or a string containing a Python
@@ -34,6 +36,9 @@ def simple_eval(node_or_string, namespace=None):
3436
* variable names causing lookups in the passed in namespace or builtins
3537
* getitem calls using the [] syntax on objects of the types above
3638
39+
Like the Python 3 (and unlike the Python 2) literal_eval, unary and binary
40+
+ and - operations are allowed on all builtin numeric types.
41+
3742
The optional namespace dict-like ought not to cause side effects on lookup
3843
"""
3944
# Based heavily on stdlib ast.literal_eval
@@ -43,8 +48,13 @@ def simple_eval(node_or_string, namespace=None):
4348
node_or_string = ast.parse(node_or_string, mode='eval')
4449
if isinstance(node_or_string, ast.Expression):
4550
node_or_string = node_or_string.body
51+
52+
string_type_nodes = (ast.Str, ast.Bytes) if py3 else (ast.Str,)
53+
name_type_nodes = (ast.Name, ast.NameConstant) if py3 else (ast.Name,)
54+
numeric_types = (int, float, complex) + (() if py3 else (long,))
55+
4656
def _convert(node):
47-
if isinstance(node, ast.Str):
57+
if isinstance(node, string_type_nodes):
4858
return node.s
4959
elif isinstance(node, ast.Num):
5060
return node.n
@@ -55,23 +65,41 @@ def _convert(node):
5565
elif isinstance(node, ast.Dict):
5666
return dict((_convert(k), _convert(v)) for k, v
5767
in zip(node.keys, node.values))
58-
elif isinstance(node, ast.Name):
68+
69+
# this is a deviation from literal_eval: we allow non-literals
70+
elif isinstance(node, name_type_nodes):
5971
try:
6072
return namespace[node.id]
6173
except KeyError:
62-
return getattr(builtins, node.id)
74+
try:
75+
return getattr(builtins, node.id)
76+
except AttributeError:
77+
raise EvaluationError("can't lookup %s" % node.id)
78+
79+
# unary + and - are allowed on any type
80+
elif isinstance(node, ast.UnaryOp) and \
81+
isinstance(node.op, (ast.UAdd, ast.USub)):
82+
# ast.literal_eval does ast typechecks here, we use type checks
83+
operand = _convert(node.operand)
84+
if not type(operand) in numeric_types:
85+
raise ValueError("unary + and - only allowed on builtin nums")
86+
if isinstance(node.op, ast.UAdd):
87+
return + operand
88+
else:
89+
return - operand
6390
elif isinstance(node, ast.BinOp) and \
64-
isinstance(node.op, (ast.Add, ast.Sub)) and \
65-
isinstance(node.right, ast.Num) and \
66-
isinstance(node.right.n, complex) and \
67-
isinstance(node.left, ast.Num) and \
68-
isinstance(node.left.n, (int, long, float)):
69-
left = node.left.n
70-
right = node.right.n
91+
isinstance(node.op, (ast.Add, ast.Sub)):
92+
# ast.literal_eval does ast typechecks here, we use type checks
93+
left = _convert(node.left)
94+
right = _convert(node.right)
95+
if not (type(left) in numeric_types and type(right) in numeric_types):
96+
raise ValueError("binary + and - only allowed on builtin nums")
7197
if isinstance(node.op, ast.Add):
7298
return left + right
7399
else:
74100
return left - right
101+
102+
# this is a deviation from literal_eval: we allow indexing
75103
elif isinstance(node, ast.Subscript) and \
76104
isinstance(node.slice, ast.Index):
77105
obj = _convert(node.value)
@@ -84,7 +112,10 @@ def _convert(node):
84112

85113
def safe_getitem(obj, index):
86114
if type(obj) in (list, tuple, dict, bytes) + string_types:
87-
return obj[index]
115+
try:
116+
return obj[index]
117+
except (KeyError, IndexError):
118+
raise EvaluationError("can't lookup key %r on %r" % (index, obj))
88119
raise ValueError('unsafe to lookup on object of type %s' % (type(obj), ))
89120

90121

@@ -141,5 +172,5 @@ def parse_trees(cursor_offset, line):
141172
raise EvaluationError("Corresponding ASTs to right of cursor are invalid")
142173
try:
143174
return simple_eval(largest_ast, namespace)
144-
except (ValueError, KeyError, IndexError, AttributeError):
175+
except ValueError:
145176
raise EvaluationError("Could not safely evaluate")

bpython/test/test_simpleeval.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def test_function_calls_raise(self):
6565
simple_eval('1()')
6666

6767
def test_nonexistant_names_raise(self):
68-
with self.assertRaises((KeyError, AttributeError)):
68+
with self.assertRaises(EvaluationError):
6969
simple_eval('a')
7070

7171
class TestEvaluateCurrentExpression(unittest.TestCase):

0 commit comments

Comments
 (0)