# HG changeset patch # User Paul Boddie # Date 1284677312 -7200 # Node ID 21fee0baed9f49d22019d2efd6c3bbf050fc467d # Parent 29661f77280bcc7bfd499633182a72bc9cb52836 Added branch suspension where "break" statements cause usage information for a branch to be suspended until after a loop has been inspected. Moved dictionary merging code into a common function. Fixed an existing test of abandoned attribute usage. Added a link to a description of RPython and Shed Skin restrictions. diff -r 29661f77280b -r 21fee0baed9f docs/related.txt --- a/docs/related.txt Thu Sep 16 20:02:26 2010 +0200 +++ b/docs/related.txt Fri Sep 17 00:48:32 2010 +0200 @@ -7,6 +7,7 @@ other languages: http://codespeak.net/pypy/dist/pypy/doc/coding-guide.html#restricted-python +http://groups.google.com/group/shedskin-discuss/msg/3f6a4ff34561a97c?dmode=source&output=gplain CapPython limits attribute access in order to facilitate code verification: diff -r 29661f77280b -r 21fee0baed9f micropython/common.py --- a/micropython/common.py Thu Sep 16 20:02:26 2010 +0200 +++ b/micropython/common.py Fri Sep 17 00:48:32 2010 +0200 @@ -26,6 +26,21 @@ except NameError: from sets import Set as set +def merge_set_dicts(dicts): + + "Merge the given 'dicts' mapping keys to sets of objects." + + new_dict = {} + + for old_dict in dicts: + for key, values in old_dict.items(): + if not new_dict.has_key(key): + new_dict[key] = set(values) + else: + new_dict[key].update(values) + + return new_dict + # Visitors and activities related to node annotations. class ASTVisitor(compiler.visitor.ASTVisitor): diff -r 29661f77280b -r 21fee0baed9f micropython/data.py --- a/micropython/data.py Thu Sep 16 20:02:26 2010 +0200 +++ b/micropython/data.py Fri Sep 17 00:48:32 2010 +0200 @@ -53,7 +53,7 @@ """ from micropython.program import DataObject, DataValue, ReplaceableContext, PlaceholderContext -from micropython.common import AtLeast, InspectError +from micropython.common import AtLeast, InspectError, merge_set_dicts def shortrepr(obj): if obj is None: @@ -103,6 +103,10 @@ self.attribute_users = [{}] # stack of assignments and branches self.attribute_user_shelves = [] + # Suspended user details plus loop details. + + self.suspended_users = [] # stack of lists of users + # Scope usage, indicating the origin of names. self.scope_usage = [{}] # stack of scope usage @@ -580,7 +584,7 @@ # Branch management methods. - def _new_branchpoint(self): + def _new_branchpoint(self, is_loop=0): """ Establish a new branchpoint where several control-flow branches diverge @@ -590,6 +594,9 @@ self.attribute_user_shelves.append([]) self.scope_shelves.append([]) + if is_loop: + self.suspended_users.append([]) + def _new_branch(self, node): """ @@ -641,6 +648,16 @@ self.attribute_users[-1] = AbandonedBranchUsers() self.scope_usage[-1] = AbandonedBranchScope() + def _suspend_branch(self): + + """ + Suspend a branch for resumption after the current loop. + """ + + users = self.suspended_users[-1] + users.append(self.attribute_users[-1]) + self._abandon_branch() + def _shelve_branch(self): """ @@ -667,29 +684,8 @@ # Combine the attribute users. This ensures that a list of users # affected by attribute usage is maintained for the current branch. - new_users = {} - all_shelved_users = self.attribute_user_shelves.pop() - all_user_names = set() - - # Find all the names defined by the branches. - - for shelved_users in all_shelved_users: - all_user_names.update(shelved_users.keys()) - - # Copy all definitions from the branches for the names. - - for shelved_users in all_shelved_users: - for name in all_user_names: - - if shelved_users.has_key(name): - nodes = shelved_users[name] - - if not new_users.has_key(name): - new_users[name] = set(nodes) - else: - new_users[name].update(nodes) - + new_users = merge_set_dicts(all_shelved_users) self.attribute_users[-1] = new_users # Combine the scope usage. @@ -735,6 +731,18 @@ self.scope_usage[-1] = new_scope_usage + def _resume_branches(self): + + """ + Incorporate users from suspended branches into the current set of active + users. + """ + + users = self.suspended_users.pop() + current_users = self.attribute_users[-1] + new_users = merge_set_dicts(users + [current_users]) + self.attribute_users[-1] = new_users + # Scope usage methods. def define_scope(self, name, scope): diff -r 29661f77280b -r 21fee0baed9f micropython/inspect.py --- a/micropython/inspect.py Thu Sep 16 20:02:26 2010 +0200 +++ b/micropython/inspect.py Fri Sep 17 00:48:32 2010 +0200 @@ -347,8 +347,8 @@ # These are convenience methods which refer to the specific namespace's # implementation of these operations. - def new_branchpoint(self): - self.get_namespace()._new_branchpoint() + def new_branchpoint(self, is_loop=0): + self.get_namespace()._new_branchpoint(is_loop) def new_branch(self, node): self.get_namespace()._new_branch(node) @@ -356,12 +356,18 @@ def abandon_branch(self): self.get_namespace()._abandon_branch() + def suspend_branch(self): + self.get_namespace()._suspend_branch() + def shelve_branch(self): self.get_namespace()._shelve_branch() def merge_branches(self): self.get_namespace()._merge_branches() + def resume_branches(self): + self.get_namespace()._resume_branches() + def define_attribute_user(self, node): """ @@ -401,6 +407,10 @@ self.NOP(node) self.abandon_branch() + def NOP_SUSPEND(self, node): + self.NOP(node) + self.suspend_branch() + def OP(self, node): for n in node.getChildNodes(): self.dispatch(n) @@ -654,7 +664,7 @@ visitBitxor = _visitBinary - visitBreak = NOP_ABANDON + visitBreak = NOP_SUSPEND visitCallFunc = OP @@ -782,7 +792,7 @@ visitFloorDiv = _visitBinary def visitFor(self, node): - self.new_branchpoint() + self.new_branchpoint(is_loop=1) # Declare names which will be used by generated code. @@ -822,6 +832,10 @@ if node.else_ is not None: self.dispatch(node.else_) + # Any suspended branches from the loop can now be resumed. + + self.resume_branches() + return None def visitFrom(self, node): @@ -1048,7 +1062,7 @@ visitUnarySub = _visitUnary def visitWhile(self, node): - self.new_branchpoint() + self.new_branchpoint(is_loop=1) # Propagate attribute usage to branches. @@ -1078,6 +1092,10 @@ if node.else_ is not None: self.dispatch(node.else_) + # Any suspended branches from the loop can now be resumed. + + self.resume_branches() + return None visitWith = NOP diff -r 29661f77280b -r 21fee0baed9f tests/abandoned_attribute_usage_multiple_candidates_nested.py --- a/tests/abandoned_attribute_usage_multiple_candidates_nested.py Thu Sep 16 20:02:26 2010 +0200 +++ b/tests/abandoned_attribute_usage_multiple_candidates_nested.py Fri Sep 17 00:48:32 2010 +0200 @@ -8,9 +8,12 @@ class C: def f(self): + return 0 + +class D: + def f(self): return 1 -class D: def g(self): return 2 diff -r 29661f77280b -r 21fee0baed9f tests/attribute_access_type_restriction_loop_break.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/attribute_access_type_restriction_loop_break.py Fri Sep 17 00:48:32 2010 +0200 @@ -0,0 +1,41 @@ +#!/usr/bin/env python + +class C: + def e(self): + return 2 + + def f(self): + return 1 + +class D: + def e(self): + return 6 + + def f(self): + return 0 # stops the test loop + + def g(self): + return 3 + +class E: + def f(self): + return 4 + + def g(self): + return 5 + +def test_loop(obj, obj2): + while obj.f(): + obj = obj2 + obj.g() + break + else: + obj.e() + return obj.f() + +c = C() +d = D() +e = E() +result1_4 = test_loop(c, e) + +# vim: tabstop=4 expandtab shiftwidth=4