# HG changeset patch # User Paul Boddie # Date 1337296880 -7200 # Node ID d5f5db3d3636326ec4e425eb768b713b5f2aa4f1 # Parent b379d0e57dcae41a22e84a2eefd4552cfc59231e Added support for the inspection and generation of list comprehensions. Moved various common code generation routines into separate methods and adjusted the list and sequence population methods for wider re-use. diff -r b379d0e57dca -r d5f5db3d3636 micropython/ast.py --- a/micropython/ast.py Thu May 17 23:37:05 2012 +0200 +++ b/micropython/ast.py Fri May 18 01:21:20 2012 +0200 @@ -3,7 +3,7 @@ """ Translate the AST of a Python program into a more interpretable representation. -Copyright (C) 2007, 2008, 2009, 2010, 2011 Paul Boddie +Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 Paul Boddie This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -382,13 +382,84 @@ self._visitAttr(node, self.attribute_load_instructions) def visitList(self, node): + self._generateList(node, node.nodes) + + def visitListComp(self, node): self._generateList(node) + temp_list = self.optimiser.optimise_temp_storage() + + # Get the list's append method. + + self._generateAttr(node, "append", self.attribute_load_instructions) + temp_method = self.optimiser.optimise_temp_storage() + + self.visitListCompFor(node.quals[0], node.quals[1:], node.expr, temp_list, temp_method) + + # Generate a reference to the list. + + self.new_op(temp_list) - def visitListComp(self, node): raise TranslationNotImplementedError("ListComp") + self.discard_temp(temp_method) + self.discard_temp(temp_list) + + def visitListCompFor(self, node, following_quals, expr, temp_list, temp_method): + temp_iterator, next_block, exit_block, else_block = self._startFor(node) + + # Explicit dispatch to tests, following loops, expression. + + if node.ifs: + self.visitListCompIf(node.ifs[0], node.ifs[1:], following_quals, expr, temp_list, temp_method) + + # Explicit dispatch to following loops, expression. + + elif following_quals: + self.visitListCompFor(following_quals[0], following_quals[1:], expr, temp_list, temp_method) + + # Explicit dispatch to the expression. + + else: + self.dispatch(expr) - def visitListCompFor(self, node): raise TranslationNotImplementedError("ListCompFor") + # Append the value to the list. + + temp_value = self.optimiser.optimise_temp_storage() + self._generateInvocation(temp_method, (temp_list, temp_value,)) + + self._endFor(node, temp_iterator, next_block, exit_block, else_block) + + def visitListCompIf(self, node, following_tests, following_quals, expr, temp_list, temp_method): + exit_block = self.new_block() + + # Evaluate the test and jump beyond the body if it is not satisfied. + + self.dispatch(node.test) + + temp = self.optimiser.optimise_temp_storage() + self.new_op(temp) + self._generateTestBoolean(node, temp) + self.new_op(JumpIfFalse(exit_block, working="status")) - def visitListCompIf(self, node): raise TranslationNotImplementedError("ListCompIf") + # Explicit dispatch to tests, following loops, expression. + + if following_tests: + self.visitListCompIf(following_tests[0], following_tests[1:], following_quals, expr, temp_list, temp_method) + + # Explicit dispatch to following loops, expression. + + elif following_quals: + self.visitListCompFor(following_quals[0], following_quals[1:], expr, temp_list, temp_method) + + # Explicit dispatch to the expression. + + else: + self.dispatch(expr) + + # Append the value to the list. + + temp_value = self.optimiser.optimise_temp_storage() + self._generateInvocation(temp_method, (temp_list, temp_value,)) + + self.set_block(exit_block) def visitName(self, node): self._visitName(node, self.name_load_instructions) @@ -638,114 +709,9 @@ self.optimiser.optimise_unused_results() def visitFor(self, node): - next_handler_block = self.new_block() - end_handler_block = self.new_block() - exit_block = self.new_block() - next_block = self.new_block() - else_block = self.new_block() - - # Get the "list" to be iterated over, obtain its iterator. - - self._startCallFunc() - self.dispatch(node.list) - self._generateAttr(node, "__iter__", self.attribute_load_instructions) - temp_target, target, temp_context = self._generateCallFunc([], node) - self._doCallFunc(temp_target, target) - self._endCallFunc(temp_target, temp_context) - - # Use a long-lasting temporary storage slot, since any result from the - # __iter__ method will not remain around for long. - - temp_iterator = self.get_temp() - - # In the loop... - - self.set_block(next_block) - - # Handle exceptions when calling "next"... - - self.add_exception_blocks(next_handler_block, end_handler_block) - self.new_op(PushHandler(next_handler_block)) - - # Use the iterator to get the next value. - - self._startCallFunc() - self.new_op(temp_iterator) - self._generateAttr(node, "next", self.attribute_load_instructions) - temp_target, target, temp_context = self._generateCallFunc([], node) - self._doCallFunc(temp_target, target) - self._endCallFunc(temp_target, temp_context) - - # Record the value to be assigned. - - self.record_value() - - # Skip the handler where the call was successful. - - self.new_op(PopHandler(1)) - self.new_op(Jump(end_handler_block)) - - # Enter the exception handler. - - self.set_block(next_handler_block) - self.new_op(PopHandler(1)) - - # Disable the handlers. - - self.drop_exception_blocks() - - # Test for StopIteration. - - self.load_builtin("StopIteration", node) - self.new_op(CheckException(target="status")) - if node.else_ is not None: - self.new_op(JumpIfTrue(else_block, working="status")) - else: - self.new_op(JumpIfTrue(exit_block, working="status")) - - # Re-raise the exception otherwise. - - self.new_op(RaiseException()) - - # After the handler, clear the exception. - - self.set_block(end_handler_block) - - # Assign to the target. - - self.dispatch(node.assign) - self.discard_value() - - # Process the body with the current next and exit points. - - self.add_loop_blocks(next_block, exit_block) + temp_iterator, next_block, exit_block, else_block = self._startFor(node, node.else_) self.dispatch(node.body) - self.drop_loop_blocks() - - # Repeat the loop. - - self.new_op(Jump(next_block)) - - # Produce the "else" section. - - if node.else_ is not None: - self.set_block(else_block) - self.new_op(ClearException(target="exception")) - self.dispatch(node.else_) - - # After the loop... - - self.set_block(exit_block) - - else: - # After the loop... - - self.set_block(exit_block) - self.new_op(ClearException(target="exception")) - - # Compilation duties... - - self.discard_temp(temp_iterator) + self._endFor(node, temp_iterator, next_block, exit_block, else_block, node.else_) def visitIf(self, node): first = 1 diff -r b379d0e57dca -r d5f5db3d3636 micropython/inspect.py --- a/micropython/inspect.py Thu May 17 23:37:05 2012 +0200 +++ b/micropython/inspect.py Fri May 18 01:21:20 2012 +0200 @@ -3,7 +3,7 @@ """ Inspect source files, obtaining details of classes and attributes. -Copyright (C) 2007, 2008, 2009, 2010, 2011 Paul Boddie +Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 Paul Boddie This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -1063,12 +1063,14 @@ return self.OP(node) def visitListComp(self, node): - for qual in node.quals: - self.dispatch(qual) - self.dispatch(node.expr) + + # Note that explicit dispatch is performed. + + if node.quals: + self.visitListCompFor(node.quals[0], node.quals[1:], node.expr) return make_instance() - def visitListCompFor(self, node): + def visitListCompFor(self, node, following_quals, expr): self.new_branchpoint() # Declare names which will be used by generated code. @@ -1091,15 +1093,45 @@ self.new_branch(node) - for if_ in node.ifs: - self.dispatch(if_) + # Note that explicit dispatch is performed. + + if node.ifs: + self.visitListCompIf(node.ifs[0], node.ifs[1:], following_quals, expr) + elif following_quals: + self.visitListCompFor(following_quals[0], following_quals[1:], expr) + else: + self.dispatch(expr) self.shelve_branch() self.in_loop = in_loop self.merge_branches() - visitListCompIf = TEST_NOP + def visitListCompIf(self, node, following_ifs, following_quals, expr): + self.use_name("__bool__", node) + self.new_branchpoint() + + # Propagate attribute usage to branches. + + self.dispatch(node.test) + + # Note that explicit dispatch is performed. + + if following_ifs: + self.visitListCompIf(following_ifs[0], following_ifs[1:], following_quals, expr) + elif following_quals: + self.visitListCompFor(following_quals[0], following_quals[1:], expr) + else: + self.new_branch(expr) + self.dispatch(expr) + self.shelve_branch() + + # Maintain a branch for the else clause. + + self.new_branch(NullBranch()) + self.shelve_branch() + + self.merge_branches() visitMod = _visitBinary diff -r b379d0e57dca -r d5f5db3d3636 micropython/trans.py --- a/micropython/trans.py Thu May 17 23:37:05 2012 +0200 +++ b/micropython/trans.py Fri May 18 01:21:20 2012 +0200 @@ -3,7 +3,7 @@ """ Translate the AST of a Python program into a more interpretable representation. -Copyright (C) 2007, 2008, 2009, 2010, 2011 Paul Boddie +Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 Paul Boddie This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -1139,20 +1139,22 @@ # Store using 0-based index values. - self._populateSequence(temp, node) + self._populateSequence(temp, node.nodes) self.new_op(temp) self.discard_temp(temp) - def _generateList(self, node): + def _generateList(self, node, nodes=None): - "Make a list using the given program 'node'." + "Make a list using the given program 'node' and optional 'nodes'." + + nodes = nodes or [] # Make a fragment containing the list elements. - self.new_op(MakeFragment(len(node.nodes) + 1)) + self.new_op(MakeFragment(len(nodes) + 1)) temp = self.get_temp() - self._populateSequence(temp, node) + self._populateSequence(temp, nodes) self.new_op(temp) self.record_value() @@ -1171,13 +1173,13 @@ self.discard_temp(temp) self.discard_temp(list_temp) - def _populateSequence(self, temp, node, offset=0): + def _populateSequence(self, temp, nodes, offset=0): """ - Populate a sequence using the given 'temp' reference and program 'node'. + Populate a sequence using the given 'temp' reference and 'nodes'. """ - for i, n in enumerate(node.nodes): + for i, n in enumerate(nodes): self.dispatch(n) self.record_value() self._storeInSequence(temp, i, offset) @@ -1236,6 +1238,144 @@ self.set_block(end_block, [false_block, true_block]) + # Common AST operations. + + def _startFor(self, node, else_=None): + + """ + Generate the start of a loop using the given 'node' and 'else_' clause, + if defined. The iterator and next, exit and else blocks are returned. + """ + + next_handler_block = self.new_block() + end_handler_block = self.new_block() + exit_block = self.new_block() + next_block = self.new_block() + else_block = self.new_block() + + temp_iterator = self._visitForList(node) + + # In the loop... + + self.set_block(next_block) + + # Handle exceptions when calling "next"... + + self.add_exception_blocks(next_handler_block, end_handler_block) + self.new_op(PushHandler(next_handler_block)) + + # Invoke the next method. + + self._visitForNext(node, temp_iterator) + + # Record the value to be assigned. + + self.record_value() + + # Skip the handler where the call was successful. + + self.new_op(PopHandler(1)) + self.new_op(Jump(end_handler_block)) + + # Enter the exception handler. + + self.set_block(next_handler_block) + self.new_op(PopHandler(1)) + + # Disable the handlers. + + self.drop_exception_blocks() + + # Test for StopIteration. + + self.load_builtin("StopIteration", node) + self.new_op(CheckException(target="status")) + if else_ is not None: + self.new_op(JumpIfTrue(else_block, working="status")) + else: + self.new_op(JumpIfTrue(exit_block, working="status")) + + # Re-raise the exception otherwise. + + self.new_op(RaiseException()) + + # After the handler, clear the exception. + + self.set_block(end_handler_block) + + # Assign to the target. + + self.dispatch(node.assign) + self.discard_value() + + # Process the body with the current next and exit points. + + self.add_loop_blocks(next_block, exit_block) + + return temp_iterator, next_block, exit_block, else_block + + def _endFor(self, node, temp_iterator, next_block, exit_block, else_block=None, else_=None): + + """ + Generate the end of a loop for the given 'node' using the given + 'temp_iterator' and 'next_block', 'exit_block' and 'else_block' + definitions, together with an 'else_' clause, if defined. + """ + + self.drop_loop_blocks() + + # Repeat the loop. + + self.new_op(Jump(next_block)) + + # Produce the "else" section. + + if else_ is not None: + self.set_block(else_block) + self.new_op(ClearException(target="exception")) + self.dispatch(else_) + + # After the loop... + + self.set_block(exit_block) + + else: + # After the loop... + + self.set_block(exit_block) + self.new_op(ClearException(target="exception")) + + # Compilation duties... + + self.discard_temp(temp_iterator) + + def _visitForList(self, node): + + "Get the list to be iterated over, returning its iterator." + + self._startCallFunc() + self.dispatch(node.list) + self._generateAttr(node, "__iter__", self.attribute_load_instructions) + temp_target, target, temp_context = self._generateCallFunc([], node) + self._doCallFunc(temp_target, target) + self._endCallFunc(temp_target, temp_context) + + # Use a long-lasting temporary storage slot, since any result from the + # __iter__ method will not remain around for long. + + return self.get_temp() + + def _visitForNext(self, node, temp_iterator): + + "Use the iterator to get the next value." + + self._startCallFunc() + self.new_op(temp_iterator) + self._generateAttr(node, "next", self.attribute_load_instructions) + temp_target, target, temp_context = self._generateCallFunc([], node) + self._doCallFunc(temp_target, target) + self._endCallFunc(temp_target, temp_context) + def _visitPrint(self, node, function_name): self._startCallFunc() self.load_builtin(function_name, node) diff -r b379d0e57dca -r d5f5db3d3636 tests/listcomp.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/listcomp.py Fri May 18 01:21:20 2012 +0200 @@ -0,0 +1,17 @@ +#!/usr/bin/env python + +class C: + def __init__(self, x): + self.x = x + +a = [C(1), C(2), C(3)] +b = [x.x for x in a] +c = [x.x for x in a if x.x > 1] + +result1_1 = b[0] +result1_2 = b[1] +result1_3 = b[2] +result2_2 = c[0] +result2_3 = c[1] + +# vim: tabstop=4 expandtab shiftwidth=4