# HG changeset patch # User Paul Boddie # Date 1211065683 -7200 # Node ID dfeec8c157581987fefb4deeb6d3a49caec2ff5e # Parent 12f29a090aa753f077acd8196a22e23b39884da0 Revised the rules around attributes and contexts, based on new observations documented in the methods.py examples. Added a context attribute to Attr instances in order to properly simulate the revised rules. Added context changing as values are added to namespaces. Added missing context generation in invocation code generation. Tidied up the formatted representations of data objects. Fixed local variable positions. Tidied up Getattr processing when inspecting modules. Extended the tests to cover attribute access rules. diff -r 12f29a090aa7 -r dfeec8c15758 README.txt --- a/README.txt Mon May 12 23:54:16 2008 +0200 +++ b/README.txt Sun May 18 01:08:03 2008 +0200 @@ -53,10 +53,11 @@ Global name Preserved Modules provide no context - Class-originating Accessor Methods acquire the context of their - attribute -or- accessor if an instance... - Preserved or retain the original context if the - accessor is a class + Class-originating Accessor Class accessor preserves the stored + attribute -or- context; instance accessor overrides + Preserved the stored context if it is null or + belongs to the instance's class + hierarchy Instance-originating Preserved Methods retain their original context attribute @@ -78,26 +79,30 @@ LoadAddress Load attribute from Classes, functions and modules known object stored as cause the loaded attribute to be - an attribute retrieved unchanged... - whereas constants (representing - instances) cause the constant to - override the attribute's own - context + an attribute retrieved unchanged; whereas + constants (representing instances) + cause the constant to override the + attribute's own context (since all + attributes should belong to the + constant's class hierarchy) - LoadAttr Load attribute from Combine the instance as context - instance stored as an with the object from the attribute - attribute + LoadAttr Load attribute from Attributes with null contexts or + instance stored as an contexts compatible with the + attribute instance cause loaded attributes + to combine the instance as context + with the object from the + attribute; other attributes have + their context preserved LoadAttrIndex Load attribute from Classes, functions and modules as unknown object stored the unknown object accessor cause as an attribute the loaded attribute to be - retrieved unchanged... - whereas instances cause the - accessor to override the - attribute's own context + retrieved unchanged; whereas + instances cause the LoadAttr rules + to apply -Note that of the above context operations, only the decision logic in -connection with LoadAttrIndex is performed at run-time. +Consequently, a certain amount of run-time testing is required for both +LoadAttr and LoadAttrIndex. Objects ------- diff -r 12f29a090aa7 -r dfeec8c15758 micropython/ast.py --- a/micropython/ast.py Mon May 12 23:54:16 2008 +0200 +++ b/micropython/ast.py Sun May 18 01:08:03 2008 +0200 @@ -252,6 +252,7 @@ if self._have_constant_input(0): # Optimise away the constant storage if appropriate. + # The target and value loading operations are removed. if self._optimise_constant_storage(AddressInstruction, 1): return @@ -353,13 +354,11 @@ temp = self._optimise_temp_storage() - # Where a target is known and has a known context, avoid generating any - # first argument. Instance methods do not have a known target since they - # are accessed via an instance whose identity cannot generally be known - # at compile-time. + # Where a target or context are not known, load the target and context. if context is None: continue_label = self.new_label() + self.new_ops(temp) self.new_op(LoadContext()) self.new_op(CheckContext()) self.new_op(JumpIfTrue(continue_label)) @@ -371,6 +370,15 @@ self.dispatch(compiler.ast.Name("TypeError")) self.new_op(RaiseException()) self.set_label(continue_label) + + # Where an instance is known to be the context, load the context. + + elif isinstance(context, Instance): + self.new_ops(temp) + self.new_op(LoadContext()) + + # Otherwise omit the context. + else: pass # NOTE: Class methods should be supported. @@ -391,7 +399,7 @@ # NOTE: Fix context for self-accessed methods. - if context is not None and isinstance(context, Instance): + if context is not None: ncontext = 1 else: ncontext = 0 @@ -565,6 +573,9 @@ NameInstruction, AddressInstruction = classes + # Optimise away the constant storage if appropriate. + # The target and value loading operations are removed. + if self._optimise_constant_storage(NameInstruction, 0): return @@ -618,7 +629,7 @@ def _have_constant_input(self, n): last = self.last_ops(n+1) return len(last) > n and (isinstance(last[n], LoadAddress) and last[n].attr.assignments == 1 or - isinstance(last[n], LoadConst)) # and not isinstance(last[n].attr, Instance) + isinstance(last[n], LoadConst)) def _have_known_target(self): return self._have_constant_input(0) @@ -708,7 +719,7 @@ if self._should_optimise_known_target() and self._have_known_target(): last = self.last_op() target = last.attr.value - context = last.context + context = last.attr.context # Handle calls to classes. @@ -737,11 +748,19 @@ if self._should_optimise_self_access() and self._have_self_input() and \ not self.unit.is_relocated(attrname): - attr = self.unit.parent.all_attributes()[attrname] - if isinstance(attr.parent, Instance): + # Either generate an instruction operating on an instance attribute. + + try: + attr = self.unit.parent.instance_attributes()[attrname] self.new_op(AttrInstruction(attr)) - else: - self.new_op(AddressInstruction(attr, Instance())) + + # Or generate an instruction operating on a class attribute. + + except KeyError: + attr = self.unit.parent.all_attributes()[attrname] + new_attr = attr.via_instance() + self.replace_op(AddressInstruction(new_attr)) + return 1 else: return 0 diff -r 12f29a090aa7 -r dfeec8c15758 micropython/data.py --- a/micropython/data.py Mon May 12 23:54:16 2008 +0200 +++ b/micropython/data.py Sun May 18 01:08:03 2008 +0200 @@ -46,6 +46,12 @@ from micropython.common import * +def shortrepr(obj): + if obj is None: + return repr(None) + else: + return obj.__shortrepr__() + # Mix-ins and abstract classes. class NamespaceDict: @@ -104,9 +110,23 @@ "The underlying set operation associating 'name' with 'value'." if not self.namespace.has_key(name): - self.namespace[name] = Attr(None, self, name, value) + + # Attempt to fix the context. + + context = self._context(value) + self.namespace[name] = Attr(None, self, context, name, value) + return self.namespace[name] + def _context(self, value): + + "Return the context to be used when storing the given 'value'." + + if value is not None: + return value.parent + else: + return None + def __delitem__(self, name): del self.namespace[name] @@ -158,11 +178,12 @@ class Attr: - "An attribute entry having a parent as context." + "An attribute entry having a context." - def __init__(self, position, parent, name, value=None, assignments=None): + def __init__(self, position, parent, context, name, value=None, assignments=None): self.position = position self.parent = parent + self.context = context self.name = name self.value = value @@ -194,8 +215,43 @@ self.assignments += AtLeast(1) self.assignment_values.add(value) + def via_instance(self): + + """ + Return either this attribute or a replacement where it is being accessed + via an instance. + """ + + if self.context is not None: + + # Check compatibility of the context with the parent. + # Where the attribute originates within the same hierarchy, use an + # instance as the context. + + if isinstance(self.parent, Class) and isinstance(self.context, Class) and ( + self.context is self.parent or + self.context in self.parent.descendants or + self.parent in self.context.descendants): + + context = Instance() + + # Otherwise, preserve the existing context. + + else: + context = self.context + + return Attr(self.position, self.parent, context, self.name, self.value, self.assignments) + + # Unknown contexts remain in use. + + else: + return self + def __repr__(self): - return "Attr(%r, %r, %r, %r, %r)" % (self.position, self.parent, self.name, self.value, self.assignments) + return "Attr(%r, %s, %s, %r, %s, %r)" % ( + self.position, shortrepr(self.parent), shortrepr(self.context), + self.name, shortrepr(self.value), self.assignments + ) # Instances are special in that they need to be wrapped together with context in # a running program, but they are not generally constant. @@ -204,9 +260,14 @@ "A placeholder indicating the involvement of an instance." + def __init__(self): + self.parent = None + def __repr__(self): return "Instance()" + __shortrepr__ = __repr__ + class Constant: "A superclass for all constant or context-free structures." @@ -230,6 +291,8 @@ else: return "Const(%r)" % self.value + __shortrepr__ = __repr__ + # Support constants as dictionary keys in order to build constant tables. def __eq__(self, other): @@ -293,9 +356,25 @@ def __repr__(self): if self.location is not None: - return "Class(%r, %r, location=%r)" % (self.name, self.parent, self.location) + return "Class(%r, %s, location=%r)" % (self.name, shortrepr(self.parent), self.location) else: - return "Class(%r, %r)" % (self.name, self.parent) + return "Class(%r, %s)" % (self.name, shortrepr(self.parent)) + + def __shortrepr__(self): + return "Class(%r, %s)" % (self.name, shortrepr(self.parent)) + + def _context(self, value): + + "Return the context to be used when storing the given 'value'." + + if value is not None: + context = value.parent + if isinstance(context, Module): + return self + else: + return context + else: + return None def finalise_attributes(self): @@ -497,7 +576,7 @@ d = {} for i, name in enumerate(self._get_position_list(positions)): - d[name] = Attr(i, Instance(), name, None) + d[name] = Attr(i, Instance(), None, name, None) return d def _cmp_positions(self, a, b): @@ -607,16 +686,21 @@ def __repr__(self): if self.location is not None: - return "Function(%r, %r, %r, %r, %r, %r, location=%r)" % ( - self.name, self.parent, self.argnames, self.defaults, self.has_star, self.has_dstar, self.location + return "Function(%r, %s, %r, %r, %r, %r, location=%r)" % ( + self.name, shortrepr(self.parent), self.argnames, self.defaults, self.has_star, self.has_dstar, self.location ) else: - return "Function(%r, %r, %r, %r, %r, %r)" % ( - self.name, self.parent, self.argnames, self.defaults, self.has_star, self.has_dstar + return "Function(%r, %s, %r, %r, %r, %r)" % ( + self.name, shortrepr(self.parent), self.argnames, self.defaults, self.has_star, self.has_dstar ) + def __shortrepr__(self): + return "Function(%r, %s)" % ( + self.name, shortrepr(self.parent) + ) + def store_default(self, value): - attr = Attr(None, self, None, value) + attr = Attr(None, self, None, None, value) attr.update(value, 1) self.default_attrs.append(attr) @@ -689,15 +773,15 @@ self[name].position = i if i is not None: - j = i + j = i + 1 else: j = 0 - i = -1 + i = 0 for i, attr in enumerate(self.locals().values()): attr.position = i + j - self.stack_local_usage = i + 1 + self.stack_local_usage = i def function_from_method(self): @@ -726,6 +810,8 @@ def __repr__(self): return "UnresolvedName(%r, %r)" % (self.name, self.parent_name) + __shortrepr__ = __repr__ + def full_name(self): if self.name is not None: return self.parent_name + "." + self.name @@ -772,6 +858,9 @@ else: return "Module(%r)" % self.name + def __shortrepr__(self): + return "Module(%r)" % self.name + # Attribute methods. "Return the module attribute names provided by the module." diff -r 12f29a090aa7 -r dfeec8c15758 micropython/inspect.py --- a/micropython/inspect.py Mon May 12 23:54:16 2008 +0200 +++ b/micropython/inspect.py Sun May 18 01:08:03 2008 +0200 @@ -368,10 +368,12 @@ expr = self.dispatch(node.expr) if isinstance(expr, Attr): value = expr.value - if isinstance(value, Module): + if isinstance(value, (Class, Module)): return value.namespace.get(node.attrname) elif isinstance(value, UnresolvedName): return UnresolvedName(node.attrname, value.full_name(), self) + else: + return None if self.builtins is not None: return self.builtins.get(node.attrname) else: diff -r 12f29a090aa7 -r dfeec8c15758 micropython/rsvp.py --- a/micropython/rsvp.py Mon May 12 23:54:16 2008 +0200 +++ b/micropython/rsvp.py Sun May 18 01:08:03 2008 +0200 @@ -27,12 +27,8 @@ stack_usage = 0 - def __init__(self, attr=None, context=None): + def __init__(self, attr=None): self.attr = attr - if self.attr is not None and isinstance(self.attr, Attr): - self.context = context or attr.parent - else: - self.context = context def __repr__(self): if self.attr is not None: diff -r 12f29a090aa7 -r dfeec8c15758 tests/call_method.py --- a/tests/call_method.py Mon May 12 23:54:16 2008 +0200 +++ b/tests/call_method.py Sun May 18 01:08:03 2008 +0200 @@ -3,6 +3,8 @@ class C: def f(self, a, b, c): self.g(a) + m = self.g + m(b) def g(self, x): C.h(self, x) diff -r 12f29a090aa7 -r dfeec8c15758 tests/methods.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/methods.py Sun May 18 01:08:03 2008 +0200 @@ -0,0 +1,46 @@ +#!/usr/bin/env python + +def f(x): + pass + +class B: + def f(self): + pass + + Bf = f # context == parent + +b = B() + + # on A on a +class A: + f1 = f # unbound (A) bound (a) + f2 = B.f # unbound (B) unbound (B) + f3 = b.f # bound (b) bound (b) + Bf = B.Bf # unbound (B) unbound (B) + + def __init__(self): + self.f4 = f # N/A function + self.f5 = B.f # N/A unbound (B) + self.f6 = b.f # N/A bound (b) + + def m(self): + self.f1 + self.f2 + self.f3 + self.f4 + self.f5 + self.f6 + +a = A() + +A.f1 +A.f2 +A.f3 +a.f1 +a.f2 +a.f3 +a.f4 +a.f5 +a.f6 + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r 12f29a090aa7 -r dfeec8c15758 tests/reference/methods.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/reference/methods.py Sun May 18 01:08:03 2008 +0200 @@ -0,0 +1,24 @@ +#!/usr/bin/env python + +class A: + pass + +class B: + def f(self): + pass + +def f(x): + pass + +b = B() + + # on A on a +A.f1 = f # unbound bound (a) +A.f2 = B.f # unbound unbound +A.f3 = b.f # bound (b) bound (b) +a = A() +a.f4 = f # N/A function +a.f5 = B.f # N/A unbound +a.f6 = b.f # N/A bound (b) + +# vim: tabstop=4 expandtab shiftwidth=4