from compiler import parse, ast, pycodegen |
import py |
import __builtin__, sys |
|
passthroughex = (KeyboardInterrupt, SystemExit, MemoryError) |
|
class Failure: |
def __init__(self, node): |
self.exc, self.value, self.tb = sys.exc_info() |
self.node = node |
|
|
|
from py.__.magic.viewtype import View |
|
class Interpretable(View): |
"""A parse tree node with a few extra methods.""" |
explanation = None |
|
def is_builtin(self, frame): |
return False |
|
def eval(self, frame): |
|
try: |
expr = ast.Expression(self.__obj__) |
expr.filename = '<eval>' |
self.__obj__.filename = '<eval>' |
co = pycodegen.ExpressionCodeGenerator(expr).getCode() |
result = frame.eval(co) |
except passthroughex: |
raise |
except: |
raise Failure(self) |
self.result = result |
self.explanation = self.explanation or frame.repr(self.result) |
|
def run(self, frame): |
|
try: |
expr = ast.Module(None, ast.Stmt([self.__obj__])) |
expr.filename = '<run>' |
co = pycodegen.ModuleCodeGenerator(expr).getCode() |
frame.exec_(co) |
except passthroughex: |
raise |
except: |
raise Failure(self) |
|
def nice_explanation(self): |
|
raw_lines = (self.explanation or '').split('\n') |
|
lines = [raw_lines[0]] |
for l in raw_lines[1:]: |
if l.startswith('{') or l.startswith('}'): |
lines.append(l) |
else: |
lines[-1] += '\\n' + l |
|
result = lines[:1] |
stack = [0] |
stackcnt = [0] |
for line in lines[1:]: |
if line.startswith('{'): |
if stackcnt[-1]: |
s = 'and ' |
else: |
s = 'where ' |
stack.append(len(result)) |
stackcnt[-1] += 1 |
stackcnt.append(0) |
result.append(' +' + ' '*(len(stack)-1) + s + line[1:]) |
else: |
assert line.startswith('}') |
stack.pop() |
stackcnt.pop() |
result[stack[-1]] += line[1:] |
assert len(stack) == 1 |
return '\n'.join(result) |
|
class Name(Interpretable): |
__view__ = ast.Name |
|
def is_local(self, frame): |
co = compile('%r in locals() is not globals()' % self.name, '?', 'eval') |
try: |
return frame.is_true(frame.eval(co)) |
except passthroughex: |
raise |
except: |
return False |
|
def is_global(self, frame): |
co = compile('%r in globals()' % self.name, '?', 'eval') |
try: |
return frame.is_true(frame.eval(co)) |
except passthroughex: |
raise |
except: |
return False |
|
def is_builtin(self, frame): |
co = compile('%r not in locals() and %r not in globals()' % ( |
self.name, self.name), '?', 'eval') |
try: |
return frame.is_true(frame.eval(co)) |
except passthroughex: |
raise |
except: |
return False |
|
def eval(self, frame): |
super(Name, self).eval(frame) |
if not self.is_local(frame): |
self.explanation = self.name |
|
class Compare(Interpretable): |
__view__ = ast.Compare |
|
def eval(self, frame): |
expr = Interpretable(self.expr) |
expr.eval(frame) |
for operation, expr2 in self.ops: |
expr2 = Interpretable(expr2) |
expr2.eval(frame) |
self.explanation = "%s %s %s" % ( |
expr.explanation, operation, expr2.explanation) |
co = compile("__exprinfo_left %s __exprinfo_right" % operation, |
'?', 'eval') |
try: |
self.result = frame.eval(co, __exprinfo_left=expr.result, |
__exprinfo_right=expr2.result) |
except passthroughex: |
raise |
except: |
raise Failure(self) |
if not frame.is_true(self.result): |
break |
expr = expr2 |
|
class And(Interpretable): |
__view__ = ast.And |
|
def eval(self, frame): |
explanations = [] |
for expr in self.nodes: |
expr = Interpretable(expr) |
expr.eval(frame) |
explanations.append(expr.explanation) |
self.result = expr.result |
if not frame.is_true(expr.result): |
break |
self.explanation = '(' + ' and '.join(explanations) + ')' |
|
class Or(Interpretable): |
__view__ = ast.Or |
|
def eval(self, frame): |
explanations = [] |
for expr in self.nodes: |
expr = Interpretable(expr) |
expr.eval(frame) |
explanations.append(expr.explanation) |
self.result = expr.result |
if frame.is_true(expr.result): |
break |
self.explanation = '(' + ' or '.join(explanations) + ')' |
|
|
|
keepalive = [] |
for astclass, astpattern in { |
ast.Not : 'not __exprinfo_expr', |
ast.Invert : '(~__exprinfo_expr)', |
}.items(): |
|
class UnaryArith(Interpretable): |
__view__ = astclass |
|
def eval(self, frame, astpattern=astpattern, |
co=compile(astpattern, '?', 'eval')): |
expr = Interpretable(self.expr) |
expr.eval(frame) |
self.explanation = astpattern.replace('__exprinfo_expr', |
expr.explanation) |
try: |
self.result = frame.eval(co, __exprinfo_expr=expr.result) |
except passthroughex: |
raise |
except: |
raise Failure(self) |
|
keepalive.append(UnaryArith) |
|
|
for astclass, astpattern in { |
ast.Add : '(__exprinfo_left + __exprinfo_right)', |
ast.Sub : '(__exprinfo_left - __exprinfo_right)', |
ast.Mul : '(__exprinfo_left * __exprinfo_right)', |
ast.Div : '(__exprinfo_left / __exprinfo_right)', |
ast.Mod : '(__exprinfo_left % __exprinfo_right)', |
ast.Power : '(__exprinfo_left ** __exprinfo_right)', |
}.items(): |
|
class BinaryArith(Interpretable): |
__view__ = astclass |
|
def eval(self, frame, astpattern=astpattern, |
co=compile(astpattern, '?', 'eval')): |
left = Interpretable(self.left) |
left.eval(frame) |
right = Interpretable(self.right) |
right.eval(frame) |
self.explanation = (astpattern |
.replace('__exprinfo_left', left .explanation) |
.replace('__exprinfo_right', right.explanation)) |
try: |
self.result = frame.eval(co, __exprinfo_left=left.result, |
__exprinfo_right=right.result) |
except passthroughex: |
raise |
except: |
raise Failure(self) |
|
keepalive.append(BinaryArith) |
|
|
class CallFunc(Interpretable): |
__view__ = ast.CallFunc |
|
def is_bool(self, frame): |
co = compile('isinstance(__exprinfo_value, bool)', '?', 'eval') |
try: |
return frame.is_true(frame.eval(co, __exprinfo_value=self.result)) |
except passthroughex: |
raise |
except: |
return False |
|
def eval(self, frame): |
node = Interpretable(self.node) |
node.eval(frame) |
explanations = [] |
vars = {'__exprinfo_fn': node.result} |
source = '__exprinfo_fn(' |
for a in self.args: |
if isinstance(a, ast.Keyword): |
keyword = a.name |
a = a.expr |
else: |
keyword = None |
a = Interpretable(a) |
a.eval(frame) |
argname = '__exprinfo_%d' % len(vars) |
vars[argname] = a.result |
if keyword is None: |
source += argname + ',' |
explanations.append(a.explanation) |
else: |
source += '%s=%s,' % (keyword, argname) |
explanations.append('%s=%s' % (keyword, a.explanation)) |
if self.star_args: |
star_args = Interpretable(self.star_args) |
star_args.eval(frame) |
argname = '__exprinfo_star' |
vars[argname] = star_args.result |
source += '*' + argname + ',' |
explanations.append('*' + star_args.explanation) |
if self.dstar_args: |
dstar_args = Interpretable(self.dstar_args) |
dstar_args.eval(frame) |
argname = '__exprinfo_kwds' |
vars[argname] = dstar_args.result |
source += '**' + argname + ',' |
explanations.append('**' + dstar_args.explanation) |
self.explanation = "%s(%s)" % ( |
node.explanation, ', '.join(explanations)) |
if source.endswith(','): |
source = source[:-1] |
source += ')' |
co = compile(source, '?', 'eval') |
try: |
self.result = frame.eval(co, **vars) |
except passthroughex: |
raise |
except: |
raise Failure(self) |
if not node.is_builtin(frame) or not self.is_bool(frame): |
r = frame.repr(self.result) |
self.explanation = '%s\n{%s = %s\n}' % (r, r, self.explanation) |
|
class Getattr(Interpretable): |
__view__ = ast.Getattr |
|
def eval(self, frame): |
expr = Interpretable(self.expr) |
expr.eval(frame) |
co = compile('__exprinfo_expr.%s' % self.attrname, '?', 'eval') |
try: |
self.result = frame.eval(co, __exprinfo_expr=expr.result) |
except passthroughex: |
raise |
except: |
raise Failure(self) |
self.explanation = '%s.%s' % (expr.explanation, self.attrname) |
|
co = compile('hasattr(__exprinfo_expr, "__dict__") and ' |
'%r in __exprinfo_expr.__dict__' % self.attrname, |
'?', 'eval') |
try: |
from_instance = frame.is_true( |
frame.eval(co, __exprinfo_expr=expr.result)) |
except passthroughex: |
raise |
except: |
from_instance = True |
if from_instance: |
r = frame.repr(self.result) |
self.explanation = '%s\n{%s = %s\n}' % (r, r, self.explanation) |
|
|
import __builtin__ |
BuiltinAssertionError = __builtin__.AssertionError |
|
class Assert(Interpretable): |
__view__ = ast.Assert |
|
def run(self, frame): |
test = Interpretable(self.test) |
test.eval(frame) |
|
if (test.explanation.startswith('False\n{False = ') and |
test.explanation.endswith('\n}')): |
test.explanation = test.explanation[15:-2] |
|
self.result = test.result |
self.explanation = 'assert ' + test.explanation |
if not frame.is_true(test.result): |
try: |
raise BuiltinAssertionError |
except passthroughex: |
raise |
except: |
raise Failure(self) |
|
class Assign(Interpretable): |
__view__ = ast.Assign |
|
def run(self, frame): |
expr = Interpretable(self.expr) |
expr.eval(frame) |
self.result = expr.result |
self.explanation = '... = ' + expr.explanation |
|
ass = ast.Assign(self.nodes, ast.Name('__exprinfo_expr')) |
mod = ast.Module(None, ast.Stmt([ass])) |
mod.filename = '<run>' |
co = pycodegen.ModuleCodeGenerator(mod).getCode() |
try: |
frame.exec_(co, __exprinfo_expr=expr.result) |
except passthroughex: |
raise |
except: |
raise Failure(self) |
|
class Discard(Interpretable): |
__view__ = ast.Discard |
|
def run(self, frame): |
expr = Interpretable(self.expr) |
expr.eval(frame) |
self.result = expr.result |
self.explanation = expr.explanation |
|
class Stmt(Interpretable): |
__view__ = ast.Stmt |
|
def run(self, frame): |
for stmt in self.nodes: |
stmt = Interpretable(stmt) |
stmt.run(frame) |
|
|
def report_failure(e): |
explanation = e.node.nice_explanation() |
if explanation: |
explanation = ", in: " + explanation |
else: |
explanation = "" |
print "%s: %s%s" % (e.exc.__name__, e.value, explanation) |
|
def check(s, frame=None): |
if frame is None: |
import sys |
frame = sys._getframe(1) |
frame = py.code.Frame(frame) |
expr = parse(s, 'eval') |
assert isinstance(expr, ast.Expression) |
node = Interpretable(expr.node) |
try: |
node.eval(frame) |
except passthroughex: |
raise |
except Failure, e: |
report_failure(e) |
else: |
if not frame.is_true(node.result): |
print "assertion failed:", node.nice_explanation() |
|
|
|
|
|
|
def interpret(source, frame, should_fail=False): |
module = Interpretable(parse(source, 'exec').node) |
|
if isinstance(frame, py.std.types.FrameType): |
frame = py.code.Frame(frame) |
try: |
module.run(frame) |
except Failure, e: |
return getfailure(e) |
except passthroughex: |
raise |
except: |
import traceback |
traceback.print_exc() |
if should_fail: |
return "(inconsistently failed then succeeded)" |
else: |
return None |
|
def getmsg(excinfo): |
if isinstance(excinfo, tuple): |
excinfo = py.code.ExceptionInfo(excinfo) |
|
|
|
|
tb = excinfo.traceback[-1] |
source = str(tb.statement).strip() |
x = interpret(source, tb.frame, should_fail=True) |
if not isinstance(x, str): |
raise TypeError, "interpret returned non-string %r" % (x,) |
return x |
|
def getfailure(e): |
explanation = e.node.nice_explanation() |
if str(e.value): |
lines = explanation.split('\n') |
lines[0] += " << %s" % (e.value,) |
explanation = '\n'.join(lines) |
text = "%s: %s" % (e.exc.__name__, explanation) |
if text.startswith('AssertionError: assert '): |
text = text[16:] |
return text |
|
def run(s, frame=None): |
if frame is None: |
import sys |
frame = sys._getframe(1) |
frame = py.code.Frame(frame) |
module = Interpretable(parse(s, 'exec').node) |
try: |
module.run(frame) |
except Failure, e: |
report_failure(e) |
|
|
if __name__ == '__main__': |
|
def f(): |
return 5 |
def g(): |
return 3 |
def h(x): |
return 'never' |
check("f() * g() == 5") |
check("not f()") |
check("not (f() and g() or 0)") |
check("f() == g()") |
i = 4 |
check("i == f()") |
check("len(f()) == 0") |
check("isinstance(2+3+4, float)") |
|
run("x = i") |
check("x == 5") |
|
run("assert not f(), 'oops'") |
run("a, b, c = 1, 2") |
run("a, b, c = f()") |
|
check("max([f(),g()]) == 4") |
check("'hello'[g()] == 'h'") |
run("'guk%d' % h(f())") |
|