# HG changeset patch # User paulb@localhost.localdomain # Date 1164670632 -3600 # Node ID 8a7125282e77c8c7e7dda11a33562bfbedf6fb08 # Parent 6f50625c615f88df3ebbcaa7f1c911fc8b18fa8b Introduced ReturnFromFunction and ReturnFromBlock nodes, replacing usage of Return nodes and improving the handling of locals snapshots and return values. Added pop-up elements for certain keywords in the viewer. Expanded operation node support. Tidied the construction of various node representations. Reordered various methods for potentially easier navigation. Expanded tests, adding for2.py: a test of return values within blocks. diff -r 6f50625c615f -r 8a7125282e77 annotate.py --- a/annotate.py Mon Nov 27 01:06:38 2006 +0100 +++ b/annotate.py Tue Nov 28 00:37:12 2006 +0100 @@ -485,6 +485,9 @@ self.namespace.snapshot() return return_ + visitReturnFromBlock = visitReturn + visitReturnFromFunction = visitReturn + def visitStoreAttr(self, storeattr): storeattr.expr = self.dispatch(storeattr.expr) expr = self.namespace.types @@ -718,8 +721,23 @@ if getattr(invoke, "share_locals", 0): self.namespace.reset() + + # Merge the locals snapshots. + for locals in self.returned_locals: - self.namespace.merge_namespace(locals, everything=0) + + # For blocks returning values (such as operations), do not merge + # snapshots or results. + + if getattr(target, "returns_value", 0): + self.namespace.merge_namespace(locals, everything=0) + + # For blocks not returning values (such as loops), merge + # snapshots and results since they contain details of genuine + # returns. + + else: + self.namespace.merge_namespace(locals) # Incorporate any raised exceptions. diff -r 6f50625c615f -r 8a7125282e77 simplified.py --- a/simplified.py Mon Nov 27 01:06:38 2006 +0100 +++ b/simplified.py Tue Nov 28 00:37:12 2006 +0100 @@ -281,7 +281,6 @@ # These are the supported "operations" described by simplified program nodes. class Pass(Node): "A placeholder node corresponding to pass." -class Return(Node): "Return an evaluated expression." class Assign(Node): "A grouping node for assignment-related operations." class Keyword(Node): "A grouping node for keyword arguments." class Global(Node): "A global name designator." @@ -301,6 +300,21 @@ class Not(Node): "A negation of an expression." class Invoke(Node): "An invocation." +# There are two types of return node: return from function and return from +# block. + +class Return(Node): + + "Return an evaluated expression." + + pass + +class ReturnFromFunction(Return): + pass + +class ReturnFromBlock(Return): + pass + # Some behaviour is set as the default in conditional nodes but may be # overridden. diff -r 6f50625c615f -r 8a7125282e77 simplify.py --- a/simplify.py Mon Nov 27 01:06:38 2006 +0100 +++ b/simplify.py Tue Nov 28 00:37:12 2006 +0100 @@ -97,16 +97,16 @@ # Placeholder or deletion transformations. - def visitStmt(self, stmt): - return self.dispatches(stmt.nodes) + def visitDiscard(self, discard): + return self.dispatch(discard.expr) def visitPass(self, pass_): return Pass(pass_, 1) - def visitDiscard(self, discard): - return self.dispatch(discard.expr) + def visitStmt(self, stmt): + return self.dispatches(stmt.nodes) - # Relatively trivial transformations. + # Top-level transformation. def visitModule(self, module, name=None): @@ -140,34 +140,36 @@ result.code = init_code + module_code return result - def visitGetattr(self, getattr): - result = LoadAttr(getattr, 1, - name=getattr.attrname, - expr=self.dispatch(getattr.expr) - ) + # Relatively trivial transformations. + + def _visitBuiltin(self, builtin, name): + result = InvokeFunction(builtin, 1, expr=LoadName(name=name), args=self.dispatches(builtin.nodes), star=None, dstar=None) + return result + + def visitBreak(self, break_): + result = ReturnFromBlock(break_, 1) return result - def visitKeyword(self, keyword): - result = Keyword(keyword, 1, - name=keyword.name, - expr=self.dispatch(keyword.expr) + def visitConst(self, const): + if not self.constants.has_key(const.value): + self.constants[const.value] = Constant(name=repr(const.value), value=const.value) + result = LoadRef(const, 1, ref=self.constants[const.value]) + return result + + def visitContinue(self, continue_): + result = InvokeBlock(continue_, 1, + expr=LoadRef(ref=self.current_subprograms[-1]) ) return result - def visitGlobal(self, global_): - result = Global(global_, 1, - names=global_.names - ) - return result - - def visitImport(self, import_): - result = Assign(import_, 1) - code = [] - for path, alias in import_.names: - importer = Import(name=path) - top = alias or path.split(".")[0] - code.append(StoreName(expr=importer, name=top)) - result.code = code + def visitDict(self, dict): + result = InvokeFunction(dict, 1, expr=LoadName(name="dict"), star=None, dstar=None) + args = [] + for key, value in dict.items: + tuple = InvokeFunction(expr=LoadName(name="tuple"), star=None, dstar=None) + tuple.set_args([self.dispatch(key), self.dispatch(value)]) + args.append(tuple) + result.set_args(args) return result def visitFrom(self, from_): @@ -187,32 +189,43 @@ result.code = code return result - def visitName(self, name): - result = LoadName(name, 1, name=name.name) + def visitGetattr(self, getattr): + result = LoadAttr(getattr, 1, + name=getattr.attrname, + expr=self.dispatch(getattr.expr) + ) return result - def visitConst(self, const): - if not self.constants.has_key(const.value): - self.constants[const.value] = Constant(name=repr(const.value), value=const.value) - result = LoadRef(const, 1, ref=self.constants[const.value]) - return result - - def visitReturn(self, return_): - result = Return(return_, 1, - expr=self.dispatch(return_.value) + def visitGlobal(self, global_): + result = Global(global_, 1, + names=global_.names ) return result - def visitBreak(self, break_): - result = Return(break_, 1) + def visitImport(self, import_): + result = Assign(import_, 1) + code = [] + for path, alias in import_.names: + importer = Import(name=path) + top = alias or path.split(".")[0] + code.append(StoreName(expr=importer, name=top)) + result.code = code return result - def visitContinue(self, continue_): - result = InvokeBlock(continue_, 1, - expr=LoadRef(ref=self.current_subprograms[-1]) + def visitKeyword(self, keyword): + result = Keyword(keyword, 1, + name=keyword.name, + expr=self.dispatch(keyword.expr) ) return result + def visitList(self, list): + return self._visitBuiltin(list, "list") + + def visitName(self, name): + result = LoadName(name, 1, name=name.name) + return result + def visitRaise(self, raise_): result = Raise(raise_, 1) if raise_.expr2 is None: @@ -230,26 +243,15 @@ result.traceback = None return result - def _visitBuiltin(self, builtin, name): - result = InvokeFunction(builtin, 1, expr=LoadName(name=name), args=self.dispatches(builtin.nodes), star=None, dstar=None) + def visitReturn(self, return_): + result = ReturnFromFunction(return_, 1, + expr=self.dispatch(return_.value) + ) return result def visitTuple(self, tuple): return self._visitBuiltin(tuple, "tuple") - def visitList(self, list): - return self._visitBuiltin(list, "list") - - def visitDict(self, dict): - result = InvokeFunction(dict, 1, expr=LoadName(name="dict"), star=None, dstar=None) - args = [] - for key, value in dict.items: - tuple = InvokeFunction(expr=LoadName(name="tuple"), star=None, dstar=None) - tuple.set_args([self.dispatch(key), self.dispatch(value)]) - args.append(tuple) - result.set_args(args) - return result - # Logical and comparison operations plus chained statements. def visitIf(self, if_): @@ -370,7 +372,7 @@ # Always return from conditional sections. - test.body += self.dispatch(stmt) + [Return()] + test.body += self.dispatch(stmt) + [ReturnFromBlock()] nodes.append(test) nodes = test.else_ = [] @@ -458,8 +460,8 @@ dstar=None) elif op_name == "is not": - invocation = self._visitNot( - InvokeFunction( + invocation = Not( + expr=InvokeFunction( expr=LoadName(name="__is__"), args=[previous, expr], star=None, @@ -477,19 +479,25 @@ nodes.append( Conditional( test=self._visitNot(LoadTemp()), - body=[Return(expr=LoadTemp())]) + body=[ + ReturnFromBlock(expr=LoadTemp()) + ], + else_=[ + ReleaseTemp() + # Subsequent operations go here! + ] + ) ) # Put subsequent operations in the else section of this conditional. - nodes[-1].else_ = [ReleaseTemp()] nodes = nodes[-1].else_ # For the last operation, return the result. else: nodes.append( - Return(expr=LoadTemp(release=1)) + ReturnFromBlock(expr=LoadTemp(release=1)) ) previous = expr @@ -519,8 +527,8 @@ ...to: - Subprogram -> Conditional (test) -> Return ... - (else) -> Conditional (test) -> Return ... + Subprogram -> Conditional (test) -> ReturnFromBlock ... + (else) -> Conditional (test) -> ReturnFromBlock ... (else) -> ... """ @@ -540,19 +548,30 @@ # Return from the subprogram where the test is not satisfied. if node is not last: - nodes.append(StoreTemp(expr=expr)) - #invocation = InvokeFunction(expr=LoadAttr(expr=LoadTemp(), name="__bool__"), args=[], star=None, dstar=None) - test = Conditional(test=self._visitNot(LoadTemp()), body=[Return(expr=LoadTemp())]) - nodes.append(test) + nodes += [ + StoreTemp(expr=expr), + Conditional( + test=self._visitNot(LoadTemp()), + body=[ + ReturnFromBlock( + expr=LoadTemp() + ) + ], + else_=[ + ReleaseTemp() + # Subsequent operations go here! + ] + ) + ] # Put subsequent operations in the else section of this conditional. - nodes = test.else_ = [ReleaseTemp()] + nodes = nodes[-1].else_ # For the last operation, return the result. else: - nodes.append(Return(expr=expr)) + nodes.append(ReturnFromBlock(expr=expr)) # Finish the subprogram definition. @@ -579,8 +598,8 @@ ...to: - Subprogram -> Conditional (test) -> Return ... - (else) -> Conditional (test) -> Return ... + Subprogram -> Conditional (test) -> ReturnFromBlock ... + (else) -> Conditional (test) -> ReturnFromBlock ... (else) -> ... """ @@ -602,7 +621,7 @@ if node is not last: nodes.append(StoreTemp(expr=expr)) invocation = InvokeFunction(expr=LoadAttr(expr=LoadTemp(), name="__bool__"), args=[], star=None, dstar=None) - test = Conditional(test=invocation, body=[Return(expr=LoadTemp())]) + test = Conditional(test=invocation, body=[ReturnFromBlock(expr=LoadTemp())]) nodes.append(test) # Put subsequent operations in the else section of this conditional. @@ -613,7 +632,7 @@ else: nodes.append( - Return(expr=expr) + ReturnFromBlock(expr=expr) ) # Finish the subprogram definition. @@ -650,49 +669,16 @@ # Operators. - def visitUnaryAdd(self, unaryadd): - return InvokeFunction(unaryadd, 1, - expr=LoadAttr( - expr=self.dispatch(unaryadd.expr), - name="__pos__" - ), - args=[], - star=None, - dstar=None - ) - - def visitUnarySub(self, unarysub): - return InvokeFunction(unarysub, 1, - expr=LoadAttr( - expr=self.dispatch(unarysub.expr), - name="__neg__" - ), - args=[], - star=None, - dstar=None - ) - - def visitInvert(self, invert): - return InvokeFunction(invert, 1, - expr=LoadAttr( - expr=self.dispatch(invert.expr), - name="__invert__" - ), - args=[], - star=None, - dstar=None - ) - - def visitAdd(self, add): + def _visitBinary(self, binary, left_name, right_name): """ Emulate the current mechanisms by producing nodes as follows: - InvokeBlock -> Subprogram -> Try (body) -> Return (expr) -> x.__add__(y) + InvokeBlock -> Subprogram -> Try (body) -> ReturnFromBlock (expr) -> x.__add__(y) (else) (handler) -> Conditional (test) -> CheckExc (expr) -> LoadExc (choices) -> LoadName TypeError - (body) -> Return (expr) -> y.__radd__(x) + (body) -> ReturnFromBlock (expr) -> y.__radd__(x) (else) """ @@ -700,12 +686,12 @@ self.current_subprograms.append(subprogram) subprogram.code = [ - Try(add, 1, + Try(binary, 1, body=[ - Return( + ReturnFromBlock( expr=InvokeFunction( - expr=LoadAttr(expr=self.dispatch(add.left), name="__add__"), - args=[self.dispatch(add.right)], + expr=LoadAttr(expr=self.dispatch(binary.left), name=left_name), + args=[self.dispatch(binary.right)], star=None, dstar=None) ) @@ -716,10 +702,10 @@ Conditional( test=CheckExc(expr=LoadExc(), choices=[LoadName(name="TypeError")]), body=[ - Return( + ReturnFromBlock( expr=InvokeFunction( - expr=LoadAttr(expr=self.dispatch(add.right), name="__radd__"), - args=[self.dispatch(add.left)], + expr=LoadAttr(expr=self.dispatch(binary.right), name=right_name), + args=[self.dispatch(binary.left)], star=None, dstar=None) ) @@ -734,6 +720,46 @@ result.expr = LoadRef(ref=subprogram) return result + def visitAdd(self, add): + return self._visitBinary(add, "__add__", "__radd__") + + def visitDiv(self, div): + return self._visitBinary(div, "__div__", "__rdiv__") + + def visitMul(self, mul): + return self._visitBinary(mul, "__mul__", "__rmul__") + + def visitSub(self, sub): + return self._visitBinary(sub, "__sub__", "__rsub__") + + def visitInvert(self, invert): + return InvokeFunction(invert, 1, + expr=LoadAttr( + expr=self.dispatch(invert.expr), + name="__invert__" + ), + args=[], + star=None, + dstar=None + ) + + def _visitUnary(self, unary, name): + return InvokeFunction(unary, 1, + expr=LoadAttr( + expr=self.dispatch(unary.expr), + name=name + ), + args=[], + star=None, + dstar=None + ) + + def visitUnaryAdd(self, unaryadd): + return self._visitUnary(unaryadd, "__pos__") + + def visitUnarySub(self, unarysub): + return self._visitUnary(unarysub, "__neg__") + # Assignments. augassign_methods = { @@ -1059,7 +1085,7 @@ # The class is initialised using the code found inside. - subprogram.code = self.dispatch(class_.code) + [Return()] + subprogram.code = self.dispatch(class_.code) + [ReturnFromBlock()] self.current_structures.pop() self.current_subprograms.pop() @@ -1155,7 +1181,7 @@ internal=0, returns_value=1, star=None, dstar=None) self.current_subprograms.append(subprogram) - subprogram.code = self.dispatch(function.code) + [Return()] + subprogram.code = self.dispatch(function.code) + [ReturnFromFunction()] self.current_subprograms.pop() self._visitFunction(function, subprogram) @@ -1171,7 +1197,7 @@ subprogram = Subprogram(name=None, module=self.module, internal=0, returns_value=1, star=None, dstar=None) self.current_subprograms.append(subprogram) - subprogram.code = [Return(expr=self.dispatch(lambda_.code))] + subprogram.code = [ReturnFromFunction(expr=self.dispatch(lambda_.code))] self.current_subprograms.pop() self._visitFunction(lambda_, subprogram) @@ -1200,7 +1226,7 @@ ...to: Subprogram -> Conditional (test) -> (body) -> Invoke subprogram - (else) -> Conditional (test) -> Return ... + (else) -> Conditional (test) -> ReturnFromBlock ... (else) -> ... """ @@ -1220,12 +1246,12 @@ # Return within the main section of the loop. - test.body = self.dispatch(while_.body) + [continuation, Return()] + test.body = self.dispatch(while_.body) + [continuation, ReturnFromBlock()] # Provide the else section, if present, along with an explicit return. if while_.else_ is not None: - test.else_ = self.dispatch(while_.else_) + [Return()] + test.else_ = self.dispatch(while_.else_) + [ReturnFromBlock()] # Finish the subprogram definition. @@ -1266,38 +1292,43 @@ # Always return from conditional sections/subprograms. if for_.else_ is not None: - else_stmt = self.dispatch(for_.else_) + [Return()] + else_stmt = self.dispatch(for_.else_) + [ReturnFromBlock()] else: - else_stmt = [Return()] + else_stmt = [ReturnFromBlock()] # Wrap the assignment in a try...except statement. - - try_except = Try(body=[], else_=[], finally_=[]) - test = Conditional( - test=InvokeFunction( - expr=LoadName(name="isinstance"), - args=[LoadExc(), LoadName(name="StopIteration")], - star=None, - dstar=None), - body=else_stmt, - else_=[Raise(expr=LoadExc())]) - try_except.handler = [test] + # Inside the body, add a recursive invocation to the subprogram. - assign = Assign( - code=[ - StoreTemp(expr=InvokeFunction(expr=LoadAttr(expr=LoadTemp(), name="next"), args=[], star=None, dstar=None)), - self.dispatch(for_.assign), - ReleaseTemp() - ]) - - # Inside the conditional, add a recursive invocation to the subprogram - # if the test condition was satisfied. - - continuation = InvokeBlock( - expr=LoadRef(ref=subprogram) - ) - try_except.body = [assign] + self.dispatch(for_.body) + [continuation] - subprogram.code = [try_except, Return()] + subprogram.code = [ + Try( + body=[ + Assign( + code=[ + StoreTemp(expr=InvokeFunction(expr=LoadAttr(expr=LoadTemp(), name="next"), args=[], star=None, dstar=None)), + self.dispatch(for_.assign), + ReleaseTemp() + ]) + ] + self.dispatch(for_.body) + [ + InvokeBlock( + expr=LoadRef(ref=subprogram) + ) + ], + handler=[ + Conditional( + test=InvokeFunction( + expr=LoadName(name="isinstance"), + args=[LoadExc(), LoadName(name="StopIteration")], + star=None, + dstar=None), + body=else_stmt, + else_=[Raise(expr=LoadExc())] + ) + ], + else_=[], + finally_=[] + ), + ReturnFromBlock() + ] # Finish the subprogram definition. diff -r 6f50625c615f -r 8a7125282e77 tests/for2.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/for2.py Tue Nov 28 00:37:12 2006 +0100 @@ -0,0 +1,9 @@ +def f(l): + for i in l: + if i == 3: + return 1 + elif i == 4: + break + return "1" + +x = f([1, 2, 3, 4, 5]) diff -r 6f50625c615f -r 8a7125282e77 tests/operators.py --- a/tests/operators.py Mon Nov 27 01:06:38 2006 +0100 +++ b/tests/operators.py Tue Nov 28 00:37:12 2006 +0100 @@ -1,2 +1,2 @@ -a = 1 + 1.1 -b = a + 2 +a = 1 + 1.1 - 1 +b = a + 2 / 2 diff -r 6f50625c615f -r 8a7125282e77 viewer.py --- a/viewer.py Mon Nov 27 01:06:38 2006 +0100 +++ b/viewer.py Tue Nov 28 00:37:12 2006 +0100 @@ -152,7 +152,9 @@ .name, .attr, .conditional, - .operator + .operator, + .iterator, + .returns { position: relative; } @@ -161,7 +163,9 @@ .name:hover > .popup, .attr:hover > .popup, .conditional:hover > .popup, - .operator:hover > .popup + .operator:hover > .popup, + .iterator:hover > .popup, + .returns:hover > .popup { display: block; } @@ -291,9 +295,19 @@ def visitFor(self, node): self.stream.write("
\n") self.stream.write("
\n") + self.stream.write("\n") self._keyword("for") + self._popup_start() + self._invocations(node._node.code[1].expr.ref.code[0].body[0].code[0].expr) # Link to next call in subprogram. + self._popup_end() + self.stream.write("\n") self.dispatch(node.assign) + self.stream.write("\n") self._keyword("in") + self._popup_start() + self._invocations(node._node.code[0].expr) # Link to __iter__ call. + self._popup_end() + self.stream.write("\n") self.dispatch(node.list) self.stream.write(":\n") self.stream.write("
\n") @@ -390,7 +404,12 @@ def visitReturn(self, node): self.stream.write("
\n") + self.stream.write("\n") self._keyword("return") + self._popup_start() + self._types(node._node) + self._popup_end() + self.stream.write("\n") self.dispatch(node.value) self.stream.write("
\n") @@ -476,11 +495,11 @@ # Expressions. - def visitAdd(self, node): - self.stream.write("\n") + def _visitBinary(self, node, name, symbol): + self.stream.write("\n" % name) self.dispatch(node.left) self.stream.write("\n") - self.stream.write("+") + self.stream.write(symbol) self._popup_start() self.stream.write("
\n") self._invocations_list(node._node.body[0].expr) # NOTE: See visitAdd in simplify. @@ -491,6 +510,9 @@ self.dispatch(node.right) self.stream.write("") + def visitAdd(self, node): + self._visitBinary(node, "add", "+") + def visitAnd(self, node): self.stream.write("\n") first = 1 @@ -581,6 +603,9 @@ def visitConst(self, node): self.stream.write(repr(node.value)) + def visitDiv(self, node): + self._visitBinary(node, "div", "/") + def visitGetattr(self, node): self.stream.write("\n") self.dispatch(node.expr) @@ -614,6 +639,9 @@ visitList = visitAssList + def visitMul(self, node): + self._visitBinary(node, "mul", "*") + def visitName(self, node): if hasattr(node, "_node"): self._name_start(node._node.name) @@ -655,6 +683,9 @@ self.stream.write("]") self.stream.write("\n") + def visitSub(self, node): + self._visitBinary(node, "sub", "-") + def visitSubscript(self, node): self.stream.write("\n") self.dispatch(node.expr) @@ -752,7 +783,10 @@ def _op(self, op_name, op): if op is not None: - self._invocations(op) + if isinstance(op, Not): + self._invocations(op.expr) + else: + self._invocations(op) def _invocations(self, node): self.stream.write("
\n")