micropython

Changeset

506:d5f5db3d3636
2012-05-18 Paul Boddie raw files shortlog changelog graph 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.
micropython/ast.py (file) micropython/inspect.py (file) micropython/trans.py (file) tests/listcomp.py (file)
     1.1 --- a/micropython/ast.py	Thu May 17 23:37:05 2012 +0200
     1.2 +++ b/micropython/ast.py	Fri May 18 01:21:20 2012 +0200
     1.3 @@ -3,7 +3,7 @@
     1.4  """
     1.5  Translate the AST of a Python program into a more interpretable representation.
     1.6  
     1.7 -Copyright (C) 2007, 2008, 2009, 2010, 2011 Paul Boddie <paul@boddie.org.uk>
     1.8 +Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 Paul Boddie <paul@boddie.org.uk>
     1.9  
    1.10  This program is free software; you can redistribute it and/or modify it under
    1.11  the terms of the GNU General Public License as published by the Free Software
    1.12 @@ -382,13 +382,84 @@
    1.13          self._visitAttr(node, self.attribute_load_instructions)
    1.14  
    1.15      def visitList(self, node):
    1.16 +        self._generateList(node, node.nodes)
    1.17 +
    1.18 +    def visitListComp(self, node):
    1.19          self._generateList(node)
    1.20 +        temp_list = self.optimiser.optimise_temp_storage()
    1.21 +
    1.22 +        # Get the list's append method.
    1.23 +
    1.24 +        self._generateAttr(node, "append", self.attribute_load_instructions)
    1.25 +        temp_method = self.optimiser.optimise_temp_storage()
    1.26 +
    1.27 +        self.visitListCompFor(node.quals[0], node.quals[1:], node.expr, temp_list, temp_method)
    1.28 +
    1.29 +        # Generate a reference to the list.
    1.30 +
    1.31 +        self.new_op(temp_list)
    1.32  
    1.33 -    def visitListComp(self, node): raise TranslationNotImplementedError("ListComp")
    1.34 +        self.discard_temp(temp_method)
    1.35 +        self.discard_temp(temp_list)
    1.36 +
    1.37 +    def visitListCompFor(self, node, following_quals, expr, temp_list, temp_method):
    1.38 +        temp_iterator, next_block, exit_block, else_block = self._startFor(node)
    1.39 +
    1.40 +        # Explicit dispatch to tests, following loops, expression.
    1.41 +
    1.42 +        if node.ifs:
    1.43 +            self.visitListCompIf(node.ifs[0], node.ifs[1:], following_quals, expr, temp_list, temp_method)
    1.44 +
    1.45 +        # Explicit dispatch to following loops, expression.
    1.46 +
    1.47 +        elif following_quals:
    1.48 +            self.visitListCompFor(following_quals[0], following_quals[1:], expr, temp_list, temp_method)
    1.49 +
    1.50 +        # Explicit dispatch to the expression.
    1.51 +
    1.52 +        else:
    1.53 +            self.dispatch(expr)
    1.54  
    1.55 -    def visitListCompFor(self, node): raise TranslationNotImplementedError("ListCompFor")
    1.56 +            # Append the value to the list.
    1.57 +
    1.58 +            temp_value = self.optimiser.optimise_temp_storage()
    1.59 +            self._generateInvocation(temp_method, (temp_list, temp_value,))
    1.60 +
    1.61 +        self._endFor(node, temp_iterator, next_block, exit_block, else_block)
    1.62 +
    1.63 +    def visitListCompIf(self, node, following_tests, following_quals, expr, temp_list, temp_method):
    1.64 +        exit_block = self.new_block()
    1.65 +
    1.66 +        # Evaluate the test and jump beyond the body if it is not satisfied.
    1.67 +
    1.68 +        self.dispatch(node.test)
    1.69 +
    1.70 +        temp = self.optimiser.optimise_temp_storage()
    1.71 +        self.new_op(temp)
    1.72 +        self._generateTestBoolean(node, temp)
    1.73 +        self.new_op(JumpIfFalse(exit_block, working="status"))
    1.74  
    1.75 -    def visitListCompIf(self, node): raise TranslationNotImplementedError("ListCompIf")
    1.76 +        # Explicit dispatch to tests, following loops, expression.
    1.77 +
    1.78 +        if following_tests:
    1.79 +            self.visitListCompIf(following_tests[0], following_tests[1:], following_quals, expr, temp_list, temp_method)
    1.80 +
    1.81 +        # Explicit dispatch to following loops, expression.
    1.82 +
    1.83 +        elif following_quals:
    1.84 +            self.visitListCompFor(following_quals[0], following_quals[1:], expr, temp_list, temp_method)
    1.85 +
    1.86 +        # Explicit dispatch to the expression.
    1.87 +
    1.88 +        else:
    1.89 +            self.dispatch(expr)
    1.90 +
    1.91 +            # Append the value to the list.
    1.92 +
    1.93 +            temp_value = self.optimiser.optimise_temp_storage()
    1.94 +            self._generateInvocation(temp_method, (temp_list, temp_value,))
    1.95 +
    1.96 +        self.set_block(exit_block)
    1.97  
    1.98      def visitName(self, node):
    1.99          self._visitName(node, self.name_load_instructions)
   1.100 @@ -638,114 +709,9 @@
   1.101          self.optimiser.optimise_unused_results()
   1.102  
   1.103      def visitFor(self, node):
   1.104 -        next_handler_block = self.new_block()
   1.105 -        end_handler_block = self.new_block()
   1.106 -        exit_block = self.new_block()
   1.107 -        next_block = self.new_block()
   1.108 -        else_block = self.new_block()
   1.109 -
   1.110 -        # Get the "list" to be iterated over, obtain its iterator.
   1.111 -
   1.112 -        self._startCallFunc()
   1.113 -        self.dispatch(node.list)
   1.114 -        self._generateAttr(node, "__iter__", self.attribute_load_instructions)
   1.115 -        temp_target, target, temp_context = self._generateCallFunc([], node)
   1.116 -        self._doCallFunc(temp_target, target)
   1.117 -        self._endCallFunc(temp_target, temp_context)
   1.118 -
   1.119 -        # Use a long-lasting temporary storage slot, since any result from the
   1.120 -        # __iter__ method will not remain around for long.
   1.121 -
   1.122 -        temp_iterator = self.get_temp()
   1.123 -
   1.124 -        # In the loop...
   1.125 -
   1.126 -        self.set_block(next_block)
   1.127 -
   1.128 -        # Handle exceptions when calling "next"...
   1.129 -
   1.130 -        self.add_exception_blocks(next_handler_block, end_handler_block)
   1.131 -        self.new_op(PushHandler(next_handler_block))
   1.132 -
   1.133 -        # Use the iterator to get the next value.
   1.134 -
   1.135 -        self._startCallFunc()
   1.136 -        self.new_op(temp_iterator)
   1.137 -        self._generateAttr(node, "next", self.attribute_load_instructions)
   1.138 -        temp_target, target, temp_context = self._generateCallFunc([], node)
   1.139 -        self._doCallFunc(temp_target, target)
   1.140 -        self._endCallFunc(temp_target, temp_context)
   1.141 -
   1.142 -        # Record the value to be assigned.
   1.143 -
   1.144 -        self.record_value()
   1.145 -
   1.146 -        # Skip the handler where the call was successful.
   1.147 -
   1.148 -        self.new_op(PopHandler(1))
   1.149 -        self.new_op(Jump(end_handler_block))
   1.150 -
   1.151 -        # Enter the exception handler.
   1.152 -
   1.153 -        self.set_block(next_handler_block)
   1.154 -        self.new_op(PopHandler(1))
   1.155 -
   1.156 -        # Disable the handlers.
   1.157 -
   1.158 -        self.drop_exception_blocks()
   1.159 -
   1.160 -        # Test for StopIteration.
   1.161 -
   1.162 -        self.load_builtin("StopIteration", node)
   1.163 -        self.new_op(CheckException(target="status"))
   1.164 -        if node.else_ is not None:
   1.165 -            self.new_op(JumpIfTrue(else_block, working="status"))
   1.166 -        else:
   1.167 -            self.new_op(JumpIfTrue(exit_block, working="status"))
   1.168 -
   1.169 -        # Re-raise the exception otherwise.
   1.170 -
   1.171 -        self.new_op(RaiseException())
   1.172 -
   1.173 -        # After the handler, clear the exception.
   1.174 -
   1.175 -        self.set_block(end_handler_block)
   1.176 -
   1.177 -        # Assign to the target.
   1.178 -
   1.179 -        self.dispatch(node.assign)
   1.180 -        self.discard_value()
   1.181 -
   1.182 -        # Process the body with the current next and exit points.
   1.183 -
   1.184 -        self.add_loop_blocks(next_block, exit_block)
   1.185 +        temp_iterator, next_block, exit_block, else_block = self._startFor(node, node.else_)
   1.186          self.dispatch(node.body)
   1.187 -        self.drop_loop_blocks()
   1.188 -
   1.189 -        # Repeat the loop.
   1.190 -
   1.191 -        self.new_op(Jump(next_block))
   1.192 -
   1.193 -        # Produce the "else" section.
   1.194 -
   1.195 -        if node.else_ is not None:
   1.196 -            self.set_block(else_block)
   1.197 -            self.new_op(ClearException(target="exception"))
   1.198 -            self.dispatch(node.else_)
   1.199 -
   1.200 -            # After the loop...
   1.201 -
   1.202 -            self.set_block(exit_block)
   1.203 -
   1.204 -        else:
   1.205 -            # After the loop...
   1.206 -
   1.207 -            self.set_block(exit_block)
   1.208 -            self.new_op(ClearException(target="exception"))
   1.209 -
   1.210 -        # Compilation duties...
   1.211 -
   1.212 -        self.discard_temp(temp_iterator)
   1.213 +        self._endFor(node, temp_iterator, next_block, exit_block, else_block, node.else_)
   1.214  
   1.215      def visitIf(self, node):
   1.216          first = 1
     2.1 --- a/micropython/inspect.py	Thu May 17 23:37:05 2012 +0200
     2.2 +++ b/micropython/inspect.py	Fri May 18 01:21:20 2012 +0200
     2.3 @@ -3,7 +3,7 @@
     2.4  """
     2.5  Inspect source files, obtaining details of classes and attributes.
     2.6  
     2.7 -Copyright (C) 2007, 2008, 2009, 2010, 2011 Paul Boddie <paul@boddie.org.uk>
     2.8 +Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 Paul Boddie <paul@boddie.org.uk>
     2.9  
    2.10  This program is free software; you can redistribute it and/or modify it under
    2.11  the terms of the GNU General Public License as published by the Free Software
    2.12 @@ -1063,12 +1063,14 @@
    2.13          return self.OP(node)
    2.14  
    2.15      def visitListComp(self, node):
    2.16 -        for qual in node.quals:
    2.17 -            self.dispatch(qual)
    2.18 -        self.dispatch(node.expr)
    2.19 +
    2.20 +        # Note that explicit dispatch is performed.
    2.21 +
    2.22 +        if node.quals:
    2.23 +            self.visitListCompFor(node.quals[0], node.quals[1:], node.expr)
    2.24          return make_instance()
    2.25  
    2.26 -    def visitListCompFor(self, node):
    2.27 +    def visitListCompFor(self, node, following_quals, expr):
    2.28          self.new_branchpoint()
    2.29  
    2.30          # Declare names which will be used by generated code.
    2.31 @@ -1091,15 +1093,45 @@
    2.32  
    2.33          self.new_branch(node)
    2.34  
    2.35 -        for if_ in node.ifs:
    2.36 -            self.dispatch(if_)
    2.37 +        # Note that explicit dispatch is performed.
    2.38 +
    2.39 +        if node.ifs:
    2.40 +            self.visitListCompIf(node.ifs[0], node.ifs[1:], following_quals, expr)
    2.41 +        elif following_quals:
    2.42 +            self.visitListCompFor(following_quals[0], following_quals[1:], expr)
    2.43 +        else:
    2.44 +            self.dispatch(expr)
    2.45  
    2.46          self.shelve_branch()
    2.47          self.in_loop = in_loop
    2.48  
    2.49          self.merge_branches()
    2.50  
    2.51 -    visitListCompIf = TEST_NOP
    2.52 +    def visitListCompIf(self, node, following_ifs, following_quals, expr):
    2.53 +        self.use_name("__bool__", node)
    2.54 +        self.new_branchpoint()
    2.55 +
    2.56 +        # Propagate attribute usage to branches.
    2.57 +
    2.58 +        self.dispatch(node.test)
    2.59 +
    2.60 +        # Note that explicit dispatch is performed.
    2.61 +
    2.62 +        if following_ifs:
    2.63 +            self.visitListCompIf(following_ifs[0], following_ifs[1:], following_quals, expr)
    2.64 +        elif following_quals:
    2.65 +            self.visitListCompFor(following_quals[0], following_quals[1:], expr)
    2.66 +        else:
    2.67 +            self.new_branch(expr)
    2.68 +            self.dispatch(expr)
    2.69 +            self.shelve_branch()
    2.70 +
    2.71 +            # Maintain a branch for the else clause.
    2.72 +
    2.73 +            self.new_branch(NullBranch())
    2.74 +            self.shelve_branch()
    2.75 +
    2.76 +        self.merge_branches()
    2.77  
    2.78      visitMod = _visitBinary
    2.79  
     3.1 --- a/micropython/trans.py	Thu May 17 23:37:05 2012 +0200
     3.2 +++ b/micropython/trans.py	Fri May 18 01:21:20 2012 +0200
     3.3 @@ -3,7 +3,7 @@
     3.4  """
     3.5  Translate the AST of a Python program into a more interpretable representation.
     3.6  
     3.7 -Copyright (C) 2007, 2008, 2009, 2010, 2011 Paul Boddie <paul@boddie.org.uk>
     3.8 +Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 Paul Boddie <paul@boddie.org.uk>
     3.9  
    3.10  This program is free software; you can redistribute it and/or modify it under
    3.11  the terms of the GNU General Public License as published by the Free Software
    3.12 @@ -1139,20 +1139,22 @@
    3.13  
    3.14          # Store using 0-based index values.
    3.15  
    3.16 -        self._populateSequence(temp, node)
    3.17 +        self._populateSequence(temp, node.nodes)
    3.18  
    3.19          self.new_op(temp)
    3.20          self.discard_temp(temp)
    3.21  
    3.22 -    def _generateList(self, node):
    3.23 +    def _generateList(self, node, nodes=None):
    3.24  
    3.25 -        "Make a list using the given program 'node'."
    3.26 +        "Make a list using the given program 'node' and optional 'nodes'."
    3.27 +
    3.28 +        nodes = nodes or []
    3.29  
    3.30          # Make a fragment containing the list elements.
    3.31  
    3.32 -        self.new_op(MakeFragment(len(node.nodes) + 1))
    3.33 +        self.new_op(MakeFragment(len(nodes) + 1))
    3.34          temp = self.get_temp()
    3.35 -        self._populateSequence(temp, node)
    3.36 +        self._populateSequence(temp, nodes)
    3.37          self.new_op(temp)
    3.38          self.record_value()
    3.39  
    3.40 @@ -1171,13 +1173,13 @@
    3.41          self.discard_temp(temp)
    3.42          self.discard_temp(list_temp)
    3.43  
    3.44 -    def _populateSequence(self, temp, node, offset=0):
    3.45 +    def _populateSequence(self, temp, nodes, offset=0):
    3.46  
    3.47          """
    3.48 -        Populate a sequence using the given 'temp' reference and program 'node'.
    3.49 +        Populate a sequence using the given 'temp' reference and 'nodes'.
    3.50          """
    3.51  
    3.52 -        for i, n in enumerate(node.nodes):
    3.53 +        for i, n in enumerate(nodes):
    3.54              self.dispatch(n)
    3.55              self.record_value()
    3.56              self._storeInSequence(temp, i, offset)
    3.57 @@ -1236,6 +1238,144 @@
    3.58  
    3.59          self.set_block(end_block, [false_block, true_block])
    3.60  
    3.61 +    # Common AST operations.
    3.62 +
    3.63 +    def _startFor(self, node, else_=None):
    3.64 +
    3.65 +        """
    3.66 +        Generate the start of a loop using the given 'node' and 'else_' clause,
    3.67 +        if defined. The iterator and next, exit and else blocks are returned.
    3.68 +        """
    3.69 +
    3.70 +        next_handler_block = self.new_block()
    3.71 +        end_handler_block = self.new_block()
    3.72 +        exit_block = self.new_block()
    3.73 +        next_block = self.new_block()
    3.74 +        else_block = self.new_block()
    3.75 +
    3.76 +        temp_iterator = self._visitForList(node)
    3.77 +
    3.78 +        # In the loop...
    3.79 +
    3.80 +        self.set_block(next_block)
    3.81 +
    3.82 +        # Handle exceptions when calling "next"...
    3.83 +
    3.84 +        self.add_exception_blocks(next_handler_block, end_handler_block)
    3.85 +        self.new_op(PushHandler(next_handler_block))
    3.86 +
    3.87 +        # Invoke the next method.
    3.88 +
    3.89 +        self._visitForNext(node, temp_iterator)
    3.90 +
    3.91 +        # Record the value to be assigned.
    3.92 +
    3.93 +        self.record_value()
    3.94 +
    3.95 +        # Skip the handler where the call was successful.
    3.96 +
    3.97 +        self.new_op(PopHandler(1))
    3.98 +        self.new_op(Jump(end_handler_block))
    3.99 +
   3.100 +        # Enter the exception handler.
   3.101 +
   3.102 +        self.set_block(next_handler_block)
   3.103 +        self.new_op(PopHandler(1))
   3.104 +
   3.105 +        # Disable the handlers.
   3.106 +
   3.107 +        self.drop_exception_blocks()
   3.108 +
   3.109 +        # Test for StopIteration.
   3.110 +
   3.111 +        self.load_builtin("StopIteration", node)
   3.112 +        self.new_op(CheckException(target="status"))
   3.113 +        if else_ is not None:
   3.114 +            self.new_op(JumpIfTrue(else_block, working="status"))
   3.115 +        else:
   3.116 +            self.new_op(JumpIfTrue(exit_block, working="status"))
   3.117 +
   3.118 +        # Re-raise the exception otherwise.
   3.119 +
   3.120 +        self.new_op(RaiseException())
   3.121 +
   3.122 +        # After the handler, clear the exception.
   3.123 +
   3.124 +        self.set_block(end_handler_block)
   3.125 +
   3.126 +        # Assign to the target.
   3.127 +
   3.128 +        self.dispatch(node.assign)
   3.129 +        self.discard_value()
   3.130 +
   3.131 +        # Process the body with the current next and exit points.
   3.132 +
   3.133 +        self.add_loop_blocks(next_block, exit_block)
   3.134 +
   3.135 +        return temp_iterator, next_block, exit_block, else_block
   3.136 +
   3.137 +    def _endFor(self, node, temp_iterator, next_block, exit_block, else_block=None, else_=None):
   3.138 +
   3.139 +        """
   3.140 +        Generate the end of a loop for the given 'node' using the given
   3.141 +        'temp_iterator' and 'next_block', 'exit_block' and 'else_block'
   3.142 +        definitions, together with an 'else_' clause, if defined.
   3.143 +        """
   3.144 +
   3.145 +        self.drop_loop_blocks()
   3.146 +
   3.147 +        # Repeat the loop.
   3.148 +
   3.149 +        self.new_op(Jump(next_block))
   3.150 +
   3.151 +        # Produce the "else" section.
   3.152 +
   3.153 +        if else_ is not None:
   3.154 +            self.set_block(else_block)
   3.155 +            self.new_op(ClearException(target="exception"))
   3.156 +            self.dispatch(else_)
   3.157 +
   3.158 +            # After the loop...
   3.159 +
   3.160 +            self.set_block(exit_block)
   3.161 +
   3.162 +        else:
   3.163 +            # After the loop...
   3.164 +
   3.165 +            self.set_block(exit_block)
   3.166 +            self.new_op(ClearException(target="exception"))
   3.167 +
   3.168 +        # Compilation duties...
   3.169 +
   3.170 +        self.discard_temp(temp_iterator)
   3.171 +
   3.172 +    def _visitForList(self, node):
   3.173 +
   3.174 +        "Get the list to be iterated over, returning its iterator."
   3.175 +
   3.176 +        self._startCallFunc()
   3.177 +        self.dispatch(node.list)
   3.178 +        self._generateAttr(node, "__iter__", self.attribute_load_instructions)
   3.179 +        temp_target, target, temp_context = self._generateCallFunc([], node)
   3.180 +        self._doCallFunc(temp_target, target)
   3.181 +        self._endCallFunc(temp_target, temp_context)
   3.182 +
   3.183 +        # Use a long-lasting temporary storage slot, since any result from the
   3.184 +        # __iter__ method will not remain around for long.
   3.185 +
   3.186 +        return self.get_temp()
   3.187 +
   3.188 +    def _visitForNext(self, node, temp_iterator):
   3.189 +
   3.190 +        "Use the iterator to get the next value."
   3.191 +
   3.192 +        self._startCallFunc()
   3.193 +        self.new_op(temp_iterator)
   3.194 +        self._generateAttr(node, "next", self.attribute_load_instructions)
   3.195 +        temp_target, target, temp_context = self._generateCallFunc([], node)
   3.196 +        self._doCallFunc(temp_target, target)
   3.197 +        self._endCallFunc(temp_target, temp_context)
   3.198 +
   3.199      def _visitPrint(self, node, function_name):
   3.200          self._startCallFunc()
   3.201          self.load_builtin(function_name, node)
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/tests/listcomp.py	Fri May 18 01:21:20 2012 +0200
     4.3 @@ -0,0 +1,17 @@
     4.4 +#!/usr/bin/env python
     4.5 +
     4.6 +class C:
     4.7 +    def __init__(self, x):
     4.8 +        self.x = x
     4.9 +
    4.10 +a = [C(1), C(2), C(3)]
    4.11 +b = [x.x for x in a]
    4.12 +c = [x.x for x in a if x.x > 1]
    4.13 +
    4.14 +result1_1 = b[0]
    4.15 +result1_2 = b[1]
    4.16 +result1_3 = b[2]
    4.17 +result2_2 = c[0]
    4.18 +result2_3 = c[1]
    4.19 +
    4.20 +# vim: tabstop=4 expandtab shiftwidth=4