# HG changeset patch # User Paul Boddie # Date 1304203071 -7200 # Node ID e78c27763673b1cdc910030ff028eb33b18ebaf8 # Parent ef635f7f79eaf65fa2382fbe9ad34e89dd4275a9 Added support for dynamic attribute accesses by extending string constants that potentially refer to attributes with object table index information, using the new _accessor class. Such constants are produced when the _accessor class is in use, currently only when the built-in getattr function is used, mentioning the _accessor class and causing it to be incorporated into generated programs. Added tests of attribute access involving the object table and getattr, together with tests which should demonstrate strings remaining as instances of str because getattr has been replaced with a "fake" version that invokes the object table mechanisms directly. diff -r ef635f7f79ea -r e78c27763673 lib/builtins.py --- a/lib/builtins.py Sat Apr 30 23:36:09 2011 +0200 +++ b/lib/builtins.py Sun May 01 00:37:51 2011 +0200 @@ -2,7 +2,8 @@ """ Simple built-in classes and functions. Objects which provide code that shall -always be compiled should provide docstrings. +always be compiled should provide docstrings. Objects without code should be +provided by native library code. Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Paul Boddie @@ -447,7 +448,21 @@ def eval(source, globals=None, locals=None): pass def execfile(filename, globals=None, locals=None): pass def filter(function, sequence): pass -def getattr(obj, name, default=None): pass + +def getattr(obj, name, default=object()): # object() is a placeholder + + "Implementation of getattr." + + _accessor # avoid isinstance but ensure that this class is included + + try: + return _getattr(obj, name) + except AttributeError: + if default is not NotImplemented: + return default + else: + raise + def globals(): pass def hasattr(obj, name): pass def hash(obj): pass @@ -611,8 +626,19 @@ start += 1 return result +# Special implementation classes. + +class _accessor(str): + + "A string which can be used to access attributes." + + def __new__(self): + # Reserve space for an object table index. + self._index = None + # Special implementation functions. +def _getattr(obj, name): pass def _isinstance(obj, cls): pass def _print(dest, *args): pass def _printnl(dest, *args): pass diff -r ef635f7f79ea -r e78c27763673 micropython/__init__.py --- a/micropython/__init__.py Sat Apr 30 23:36:09 2011 +0200 +++ b/micropython/__init__.py Sun May 01 00:37:51 2011 +0200 @@ -226,7 +226,7 @@ # Get the raw version for the architecture. if arch_item is not None: - pos = arch_item.set_location(pos, with_builtins) + pos = arch_item.set_location(pos, objtable, with_builtins) else: pos += 1 diff -r ef635f7f79ea -r e78c27763673 micropython/data.py --- a/micropython/data.py Sat Apr 30 23:36:09 2011 +0200 +++ b/micropython/data.py Sun May 01 00:37:51 2011 +0200 @@ -1113,6 +1113,8 @@ def __hash__(self): return hash(self.value) + # Constants are instances of various built-in types. + def value_type_name(self): return ".".join(self.value_type_name_parts()) diff -r ef635f7f79ea -r e78c27763673 micropython/inspect.py --- a/micropython/inspect.py Sat Apr 30 23:36:09 2011 +0200 +++ b/micropython/inspect.py Sun May 01 00:37:51 2011 +0200 @@ -801,6 +801,8 @@ def visitConst(self, node): # Register the constant, if necessary, returning the resulting object. + # The type name is noted as being used, thus preserving the class in any + # generated program. self.use_name(self.importer.get_constant_type_name(node.value), node) return self.importer.make_constant(node.value) diff -r ef635f7f79ea -r e78c27763673 micropython/raw.py --- a/micropython/raw.py Sat Apr 30 23:36:09 2011 +0200 +++ b/micropython/raw.py Sun May 01 00:37:51 2011 +0200 @@ -3,7 +3,7 @@ """ Classes used to help with the generation of raw image data. -Copyright (C) 2009, 2010 Paul Boddie +Copyright (C) 2009, 2010, 2011 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 @@ -26,7 +26,7 @@ def __init__(self, item): self.item = item - def set_location(self, location, with_builtins): + def set_location(self, location, objtable, with_builtins): self.item.location = location return location + 1 diff -r ef635f7f79ea -r e78c27763673 micropython/rsvp.py --- a/micropython/rsvp.py Sat Apr 30 23:36:09 2011 +0200 +++ b/micropython/rsvp.py Sun May 01 00:37:51 2011 +0200 @@ -68,7 +68,7 @@ "A wrapper for blocks." - def set_location(self, location, with_builtins): + def set_location(self, location, objtable, with_builtins): item = self.item item.location = location return location + len(item.code) @@ -80,12 +80,12 @@ "A wrapper for classes." - def set_location(self, location, with_builtins): + def set_location(self, location, objtable, with_builtins): self.item.instance_template_location = location # Include the instance template and __class__ attribute in the size. - return RSVPObject.set_location(self, location + 1, with_builtins) + return RSVPObject.set_location(self, location + 1, objtable, with_builtins) def finalise_location(self, with_builtins): self._finalise_location(with_builtins) @@ -140,44 +140,82 @@ "A wrapper for constants." - def set_location(self, location, with_builtins): - location = RSVPObject.set_location(self, location, with_builtins) - return location + len(self.raw_data()) + def set_location(self, location, objtable, with_builtins): + item = self.item + value = item.get_value() + type_name = self.type_name(item.value_type_name(), value, objtable) + + location = RSVPObject.set_location(self, location, objtable, with_builtins) + return location + self.raw_size(type_name, value) def as_raw(self, objtable, paramtable, with_builtins): item = self.item - classcode = objtable.as_list().get_code(item.value_type_name()) - attrcode = objtable.get_index(item.value_type_name()) + value = item.get_value() + type_name = self.type_name(item.value_type_name(), value, objtable) + raw_data = self.raw_data(type_name, value, objtable) + size = self.raw_size(type_name, value) + 1 + + # Generate the footprint of the constant. + + classcode = objtable.as_list().get_code(type_name) + attrcode = objtable.get_index(type_name) + return [ DataObject( classcode, attrcode, # is instance None, - item.value_type_name(), - 2 # size (header plus data) + type_name, + size # header plus data ) # NOTE: The RSVP library needs changing if more attributes are added - # NOTE: here. + # NOTE: here (such as a __class__ attribute for all instances). + + ] + raw_data + + def type_name(self, type_name, value, objtable): - ] + self.raw_data() + # Change the type of string constants if they might be used as attribute + # accessors. + + accessor_cls = "__builtins__._accessor" + + if type_name == "__builtins__.str" and value in objtable.attribute_names() and \ + accessor_cls in objtable.object_names(): - def raw_data(self): - item = self.item - value = item.get_value() - # NOTE: Start simple and use single entries for most types. - if item.value_type_name() in ("__builtins__.tuple", "__builtins__.list"): + return accessor_cls + else: + return type_name + + def raw_data(self, type_name, value, objtable): + + # NOTE: Start simple and use single entries for most types, even though + # NOTE: a concrete representation may use multiple memory words to + # NOTE: represent the data. + + if type_name in ("__builtins__.tuple", "__builtins__.list"): return [len(value)] + list(value) + elif type_name == "__builtins__._accessor": + return [value, objtable.get_index(value)] else: return [value] + def raw_size(self, type_name, value): + if type_name in ("__builtins__.tuple", "__builtins__.list"): + return 1 + len(value) + elif type_name == "__builtins__._accessor": + return 2 + else: + return 1 + class RSVPFunction(RSVPObject): "A wrapper for functions." - def set_location(self, location, with_builtins): + def set_location(self, location, objtable, with_builtins): item = self.item - location = RSVPObject.set_location(self, location, with_builtins) + location = RSVPObject.set_location(self, location, objtable, with_builtins) # Set the code location only where the code has been # generated. diff -r ef635f7f79ea -r e78c27763673 rsvplib.py --- a/rsvplib.py Sat Apr 30 23:36:09 2011 +0200 +++ b/rsvplib.py Sun May 01 00:37:51 2011 +0200 @@ -44,29 +44,28 @@ # Native class constants. - cls = self.machine._get_class("__builtins__", "int") - self.int_class = cls.location - self.int_instance = cls.instance_template_location - cls = self.machine._get_class("__builtins__", "list") - if cls is not None: - self.list_class = cls.location - self.list_instance = cls.instance_template_location - cls = self.machine._get_class("__builtins__", "IndexError") - if cls is not None: - self.index_error = cls.location - self.index_error_instance = cls.instance_template_location - cls = self.machine._get_class("__builtins__", "basestring") - if cls is not None: - self.str_class = cls.location - self.str_instance = cls.instance_template_location + self.int_class, self.int_instance = self._get_builtin_class_and_template("int") + self.list_class, self.list_instance = self._get_builtin_class_and_template("list") + self.index_error, self.index_error_instance = self._get_builtin_class_and_template("IndexError") + self.str_class, self.str_instance = self._get_builtin_class_and_template("basestring") + self.accessor_class, self.accessor_instance = self._get_builtin_class_and_template("_accessor") self.tuple_class = self.machine.tuple_class self.tuple_instance = self.machine.tuple_instance + + self.attr_error_instance = self.machine.attr_error_instance self.type_error_instance = self.machine.type_error_instance self.frame_stack = self.machine.frame_stack self.local_sp_stack = self.machine.local_sp_stack + def _get_builtin_class_and_template(self, name): + cls = self.machine._get_class("__builtins__", name) + if cls is not None: + return cls.location, cls.instance_template_location + else: + return None, None + def _check_index(self, pos, nelements): return pos >= 0 and pos < nelements @@ -468,6 +467,39 @@ def builtins_object_init(self): pass + def builtins_getattr(self): + frame = self.local_sp_stack[-1] + + # Get the object, attribute name. + + obj_value = self.frame_stack[frame] + name_value = self.frame_stack[frame + 1] + + if not self.machine._CheckInstance(name_value.ref, self.accessor_class): + self.machine.exception = self.machine._MakeObject(self.instance_size, self.attr_error_instance) + return self.machine.RaiseException() + + # Get the object table index from the name. It is a bare integer, not a reference. + + index = self.machine.load(name_value.ref + self.instance_data_offset + 1) + + # NOTE: This is very much like LoadAttrIndex. + + data = self.machine.load(obj_value.ref) + element = self.machine.objlist[data.classcode + index] + + if element is not None: + attr_index, static_attr, offset = element + if attr_index == index: + if static_attr: + self.machine.result = self.machine.load(offset) # offset is address of class/module attribute + else: + self.machine.result = self.machine.load(obj_value.ref + offset) + return + + self.machine.exception = self.machine._MakeObject(self.instance_size, self.attr_error_instance) + return self.machine.RaiseException() + def builtins_isinstance(self): frame = self.local_sp_stack[-1] @@ -528,6 +560,10 @@ "__builtins__.object.__init__" : builtins_object_init, # NOTE: A no-operation. "__builtins__.BaseException.__init__" : builtins_object_init, # NOTE: To be made distinct, potentially in the builtins module. + # Native functions: + + "__builtins__._getattr" : builtins_getattr, + # Native instantiator helpers: "__builtins__.list.__new__" : builtins_list_new, diff -r ef635f7f79ea -r e78c27763673 tests/attributes_class_uncertain.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/attributes_class_uncertain.py Sun May 01 00:37:51 2011 +0200 @@ -0,0 +1,15 @@ +#!/usr/bin/env python + +class C: + x = 1 + +class D: + x = 2 + +def f(c): + return c.x + +result_1 = f(C) +result_2 = f(D) + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r ef635f7f79ea -r e78c27763673 tests/attributes_instance_uncertain.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/attributes_instance_uncertain.py Sun May 01 00:37:51 2011 +0200 @@ -0,0 +1,17 @@ +#!/usr/bin/env python + +class C: + x = 1 + +class D: + x = 2 + +def f(c): + return c.x + +c = C() +d = D() +result_1 = f(c) +result_2 = f(d) + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r ef635f7f79ea -r e78c27763673 tests/getattr_class.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/getattr_class.py Sun May 01 00:37:51 2011 +0200 @@ -0,0 +1,12 @@ +#!/usr/bin/env python + +class C: + x = 1 + +class D: + x = 2 + +result_1 = getattr(C, "x") +result_2 = getattr(D, "x") + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r ef635f7f79ea -r e78c27763673 tests/getattr_class_fake.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/getattr_class_fake.py Sun May 01 00:37:51 2011 +0200 @@ -0,0 +1,15 @@ +#!/usr/bin/env python + +class C: + x = 1 + +class D: + x = 2 + +def getattr(obj, name): + return obj.x + +result_1 = getattr(C, "x") # "x" should be a str, since no dynamic access is done +result_2 = getattr(D, "x") + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r ef635f7f79ea -r e78c27763673 tests/getattr_instance.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/getattr_instance.py Sun May 01 00:37:51 2011 +0200 @@ -0,0 +1,14 @@ +#!/usr/bin/env python + +class C: + x = 1 + +class D: + x = 2 + +c = C() +d = D() +result_1 = getattr(c, "x") +result_2 = getattr(d, "x") + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r ef635f7f79ea -r e78c27763673 tests/getattr_instance_fake.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/getattr_instance_fake.py Sun May 01 00:37:51 2011 +0200 @@ -0,0 +1,17 @@ +#!/usr/bin/env python + +class C: + x = 1 + +class D: + x = 2 + +def getattr(obj, name): + return obj.x + +c = C() +d = D() +result_1 = getattr(c, "x") # "x" should be a str, since no dynamic access is done +result_2 = getattr(d, "x") + +# vim: tabstop=4 expandtab shiftwidth=4