1.1 --- a/TO_DO.txt Thu Jan 13 23:58:12 2011 +0100
1.2 +++ b/TO_DO.txt Sat Jan 29 21:07:27 2011 +0100
1.3 @@ -1,28 +1,29 @@
1.4 +Permit __class__ as being differently defined for classes and instances. Since __class__
1.5 +is always defined, no shadowing may occur for the attribute exposing it at different
1.6 +levels.
1.7 +
1.8 + The type class should be exposed as each class's __class__ attribute.
1.9 +
1.10 + The object-relative definition of __class__ should be stored in the object table.
1.11 +
1.12 + Note that object table information will not be able to reflect the class-type
1.13 + relationship, but isinstance will need to check for instances and classes, anyway.
1.14 +
1.15 +Tuple references to stack locations in a merged stack/heap memory model.
1.16 +
1.17 +Attribute Usage
1.18 +===============
1.19 +
1.20 Consider attribute assignment observations, along with the possibility of class attribute
1.21 assignment.
1.22
1.23 -Local assignment detection plus frame re-use. Example: slice.__init__ calls
1.24 -xrange.__init__ with the same arguments which are unchanged in xrange.__init__. There is
1.25 -therefore no need to build a new frame for this call.
1.26 -
1.27 Consider attribute usage observations being suspended inside blocks where AttributeError
1.28 may be caught (although this doesn't anticipate such exceptions being caught outside a
1.29 function altogether).
1.30
1.31 -Fix object table entries for attributes not provided by any known object, or provide an
1.32 -error, potentially overridden by options. For example, the augmented assignment methods
1.33 -are not supported by the built-in objects and thus the operator module functions cause
1.34 -the compilation to fail. Alternatively, just supply the methods since something has to do
1.35 -so in the builtins.
1.36 -
1.37 Consider type deduction and its consequences where types belong to the same hierarchy
1.38 and where a guard could be generated for the most general type.
1.39
1.40 -Consider attribute merging where many attributes are just aliases for the same underlying
1.41 -definition.
1.42 -
1.43 -Consider merging the InspectedModule.store tests with the scope conflict handling.
1.44 -
1.45 Consider permitting multiple class alternatives where the attributes are all identical.
1.46
1.47 Support class attribute positioning similar to instance attribute positioning, potentially
1.48 @@ -30,10 +31,52 @@
1.49 the class attribute could be exposed at a similar relative position to the class (and
1.50 potentially accessible using a LoadAttr-style instruction).
1.51
1.52 +**** Constant attribute users need not maintain usage since they are already resolved. ****
1.53 +
1.54 +Loop entry points should capture usage to update later assignments in the loop.
1.55 +The continue and break statements should affect usage propagation.
1.56 +
1.57 +Consider handling CallFunc in micropython.inspect in order to produce instances of specific classes.
1.58 +Then, consider adding support for guard removal/verification where known instances are involved.
1.59 +Consider handling branches of values within namespaces in order to support more precise value usage.
1.60 +
1.61 +Frame Optimisations
1.62 +===================
1.63 +
1.64 +Stack frame replacement where a local frame is unused after a call, such as in a tail call
1.65 +situation.
1.66 +
1.67 +Local assignment detection plus frame re-use. Example: slice.__init__ calls
1.68 +xrange.__init__ with the same arguments which are unchanged in xrange.__init__. There is
1.69 +therefore no need to build a new frame for this call.
1.70 +
1.71 +Function Specialisation
1.72 +=======================
1.73 +
1.74 +Specialisation of certain functions, such as isinstance(x, cls) where cls is a known
1.75 +constant.
1.76 +
1.77 +Structure and Object Table Optimisations
1.78 +========================================
1.79 +
1.80 +Fix object table entries for attributes not provided by any known object, or provide an
1.81 +error, potentially overridden by options. For example, the augmented assignment methods
1.82 +are not supported by the built-in objects and thus the operator module functions cause
1.83 +the compilation to fail. Alternatively, just supply the methods since something has to do
1.84 +so in the builtins.
1.85 +
1.86 +Consider attribute merging where many attributes are just aliases for the same underlying
1.87 +definition.
1.88 +
1.89 Consider references to defaults as occurring only within the context of a particular
1.90 function, thus eliminating default value classes if such functions are not themselves
1.91 invoked.
1.92
1.93 +Scope Handling
1.94 +==============
1.95 +
1.96 +Consider merging the InspectedModule.store tests with the scope conflict handling.
1.97 +
1.98 Consider labelling _scope on assignments and dealing with the assignment of removed
1.99 attributes, possibly removing the entire assignment, and distinguishing between such cases
1.100 and unknown names.
1.101 @@ -49,19 +92,16 @@
1.102 set # could be confused by the local definition at run-time
1.103 ----
1.104
1.105 +Object Coverage
1.106 +===============
1.107 +
1.108 Support __init__ traversal (and other implicit names) more effectively.
1.109
1.110 +Other
1.111 +=====
1.112 +
1.113 Check context_value initialisation (avoiding or handling None effectively).
1.114
1.115 __getitem__ could be written in Python, using a native method only to access fragments.
1.116
1.117 Consider better "macro" support where new expressions need to be generated and processed.
1.118 -
1.119 -**** Constant attribute users need not maintain usage since they are already resolved. ****
1.120 -
1.121 -Loop entry points should capture usage to update later assignments in the loop.
1.122 -The continue and break statements should affect usage propagation.
1.123 -
1.124 -Consider handling CallFunc in micropython.inspect in order to produce instances of specific classes.
1.125 -Then, consider adding support for guard removal/verification where known instances are involved.
1.126 -Consider handling branches of values within namespaces in order to support more precise value usage.
2.1 --- a/docs/concepts.txt Thu Jan 13 23:58:12 2011 +0100
2.2 +++ b/docs/concepts.txt Sat Jan 29 21:07:27 2011 +0100
2.3 @@ -217,11 +217,10 @@
2.4 Attribute Locations
2.5 -------------------
2.6
2.7 -The locations stored in table/list elements are for instance attributes
2.8 -relative to the location of the instance, whereas those for class attributes
2.9 -and modules are absolute addresses (although these could also be changed to
2.10 -object-relative locations). Thus, each occupied table cell has the following
2.11 -structure:
2.12 +The locations stored in table/list elements are generally for instance
2.13 +attributes relative to the location of the instance, whereas those for class
2.14 +attributes and module attributes are generally absolute addresses. Thus, each
2.15 +occupied table cell has the following structure:
2.16
2.17 attrcode, uses-absolute-address, address (or location)
2.18
2.19 @@ -233,6 +232,18 @@
2.20 is a need to test for classes and modules to prevent assignment to attributes
2.21 of such objects, this particular information is always required.
2.22
2.23 +The __class__ Attribute
2.24 +-----------------------
2.25 +
2.26 +The exception to the above general rules about relative locations and absolute
2.27 +addresses involves the __class__ attribute which is defined differently for
2.28 +each class and its instances. Since the table elements can only refer to a
2.29 +single absolute address, thus providing only a single value, such absolute
2.30 +references which are sufficient for most class attributes would not be
2.31 +appropriate for the __class__ attribute. However, using an object-relative
2.32 +location would require both classes and instances to retain an attribute
2.33 +location specifically to hold the value appropriate for each object type.
2.34 +
2.35 Comparing Tables as Matrices with Displacement Lists
2.36 ----------------------------------------------------
2.37
3.1 --- a/micropython/__init__.py Thu Jan 13 23:58:12 2011 +0100
3.2 +++ b/micropython/__init__.py Sat Jan 29 21:07:27 2011 +0100
3.3 @@ -5,7 +5,7 @@
3.4 from the simplify package but has had various details related to that package
3.5 removed.
3.6
3.7 -Copyright (C) 2006, 2007, 2008, 2009, 2010 Paul Boddie <paul@boddie.org.uk>
3.8 +Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 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 @@ -142,7 +142,7 @@
3.13 # Append classes and functions to the image.
3.14
3.15 for obj in module.all_objects:
3.16 - if isinstance(obj, micropython.inspect.Class):
3.17 + if isinstance(obj, micropython.data.Class):
3.18
3.19 # Add header details.
3.20
3.21 @@ -168,7 +168,7 @@
3.22 # level, and the code location is set within the code
3.23 # generation process for the module.
3.24
3.25 - elif isinstance(obj, micropython.inspect.Function):
3.26 + elif isinstance(obj, micropython.data.Function):
3.27
3.28 # Add header details.
3.29
3.30 @@ -270,7 +270,7 @@
3.31 # with descendant information.
3.32
3.33 for obj in module.all_objects:
3.34 - if isinstance(obj, micropython.inspect.Class):
3.35 + if isinstance(obj, micropython.data.Class):
3.36
3.37 # Prevent ambiguous classes.
3.38
3.39 @@ -302,14 +302,14 @@
3.40
3.41 for module in self.importer.get_modules():
3.42 for obj in module.all_objects:
3.43 - if isinstance(obj, micropython.inspect.Function):
3.44 + if isinstance(obj, micropython.data.Function):
3.45 t.add(obj.full_name(), obj.parameters())
3.46
3.47 # Classes are callable, too.
3.48 # Take details of the appropriate __init__ method to make an
3.49 # entry for an instantiation function for the class.
3.50
3.51 - elif isinstance(obj, micropython.inspect.Class):
3.52 + elif isinstance(obj, micropython.data.Class):
3.53 t.add(obj.get_instantiator().full_name(), obj.get_instantiator().parameters())
3.54
3.55 # Filter out all parameter table entries not referenced by keyword
4.1 --- a/micropython/data.py Thu Jan 13 23:58:12 2011 +0100
4.2 +++ b/micropython/data.py Sat Jan 29 21:07:27 2011 +0100
4.3 @@ -7,7 +7,7 @@
4.4 program but which are wrapped in context-dependent structures in the running
4.5 program.
4.6
4.7 -Copyright (C) 2007, 2008, 2009, 2010 Paul Boddie <paul@boddie.org.uk>
4.8 +Copyright (C) 2007, 2008, 2009, 2010, 2011 Paul Boddie <paul@boddie.org.uk>
4.9
4.10 This program is free software; you can redistribute it and/or modify it under
4.11 the terms of the GNU General Public License as published by the Free Software
4.12 @@ -150,6 +150,9 @@
4.13 def vacuum_item(self, name):
4.14 if self.has_key(name):
4.15 del self[name]
4.16 + return 1
4.17 + else:
4.18 + return 0
4.19
4.20 def add_lambda(self, obj):
4.21 attr = Attr(None, self, obj.name)
4.22 @@ -955,7 +958,7 @@
4.23 as a class or a module.
4.24 """
4.25
4.26 - return isinstance(self.parent, (Class, Module))
4.27 + return isinstance(self.parent, (Class, Module)) and self.name != "__class__"
4.28
4.29 def defines_ambiguous_class(self):
4.30
4.31 @@ -1109,13 +1112,14 @@
4.32
4.33 class Class(NamespaceDict, Naming, Constant):
4.34
4.35 - "An inspected class."
4.36 -
4.37 - def __init__(self, name, parent, module=None, node=None):
4.38 + "A base class for common/normal classes and the type class."
4.39 +
4.40 + def __init__(self, name, parent=None, module=None, node=None):
4.41
4.42 """
4.43 - Initialise the class with the given 'name', 'parent' object, optional
4.44 - 'module' and optional AST 'node'.
4.45 + Initialise the class with the given 'name', optional 'parent' object,
4.46 + 'module' and AST 'node'. The optional information must be set at a later
4.47 + point using the 'set_context' method if omitted.
4.48 """
4.49
4.50 NamespaceDict.__init__(self, module)
4.51 @@ -1152,9 +1156,19 @@
4.52 self.local_usage = 0
4.53 self.all_local_usage = 0
4.54
4.55 - # Add this class to its attributes.
4.56 -
4.57 - self.set("__class__", self)
4.58 + # Add __class__ attributes to this class and to instances of it.
4.59 +
4.60 + if self.parent is not None:
4.61 + self.initialise_class_attribute()
4.62 + self.add_instance_attribute("__class__")
4.63 +
4.64 + def set_context(self, parent, module, node):
4.65 + self.parent = parent
4.66 + self.module = module
4.67 + self.astnode = node
4.68 +
4.69 + self.initialise_class_attribute()
4.70 + self.add_instance_attribute("__class__")
4.71
4.72 def reset_caches(self):
4.73
4.74 @@ -1204,6 +1218,17 @@
4.75
4.76 # Administrative methods.
4.77
4.78 + def items_for_vacuum(self):
4.79 + items = []
4.80 + for name in self.instattr:
4.81 + items.append((name, None))
4.82 + return NamespaceDict.items_for_vacuum(self) + items
4.83 +
4.84 + def vacuum_item(self, name):
4.85 + if not NamespaceDict.vacuum_item(self, name):
4.86 + self.instattr.remove(name)
4.87 + return 1
4.88 +
4.89 def finalise_attributes(self):
4.90
4.91 "Make sure that all attributes are fully defined."
4.92 @@ -1457,17 +1482,34 @@
4.93 """
4.94 Return all attributes for an instance, indicating either the class which
4.95 provides them or that the instance itself provides them.
4.96 +
4.97 + Note that __class__ acts like an instance attribute for both instances
4.98 + and classes.
4.99 """
4.100
4.101 if self.allattr is None:
4.102 self.allattr = {}
4.103 self.allattr.update(self.all_class_attributes())
4.104 for name, attr in self.instance_attributes().items():
4.105 - if self.allattr.has_key(name):
4.106 + if self.allattr.has_key(name) and name != "__class__":
4.107 print "Warning: instance attribute %r in %r overrides class attribute." % (name, self)
4.108 self.allattr[name] = attr
4.109 return self.allattr
4.110
4.111 +class TypeClass(Class):
4.112 +
4.113 + "A special class for the type class."
4.114 +
4.115 + def initialise_class_attribute(self):
4.116 + self.set("__class__", self)
4.117 +
4.118 +class CommonClass(Class):
4.119 +
4.120 + "An inspected class."
4.121 +
4.122 + def initialise_class_attribute(self):
4.123 + self.set("__class__", type_class)
4.124 +
4.125 class Function(NamespaceDict, Naming, Constant):
4.126
4.127 "An inspected function."
4.128 @@ -1716,6 +1758,7 @@
4.129
4.130 def vacuum_item(self, name):
4.131 del self.lambdas[name]
4.132 + return 1
4.133
4.134 def finalise_attributes(self):
4.135
4.136 @@ -1868,4 +1911,23 @@
4.137
4.138 return dict(self)
4.139
4.140 +# Pre-made instances.
4.141 +
4.142 +type_class = TypeClass("type") # details to be filled in later
4.143 +
4.144 +# Class construction.
4.145 +
4.146 +def get_class(name, parent, module, node):
4.147 +
4.148 + """
4.149 + Return a Class instance for the class with the given 'name', 'parent',
4.150 + 'module' and 'node'.
4.151 + """
4.152 +
4.153 + if name == "type" and module.full_name() == "__builtins__":
4.154 + type_class.set_context(parent, module, node)
4.155 + return type_class
4.156 + else:
4.157 + return CommonClass(name, parent, module, node)
4.158 +
4.159 # vim: tabstop=4 expandtab shiftwidth=4
5.1 --- a/micropython/inspect.py Thu Jan 13 23:58:12 2011 +0100
5.2 +++ b/micropython/inspect.py Sat Jan 29 21:07:27 2011 +0100
5.3 @@ -3,7 +3,7 @@
5.4 """
5.5 Inspect source files, obtaining details of classes and attributes.
5.6
5.7 -Copyright (C) 2007, 2008, 2009, 2010 Paul Boddie <paul@boddie.org.uk>
5.8 +Copyright (C) 2007, 2008, 2009, 2010, 2011 Paul Boddie <paul@boddie.org.uk>
5.9
5.10 This program is free software; you can redistribute it and/or modify it under
5.11 the terms of the GNU General Public License as published by the Free Software
5.12 @@ -213,7 +213,9 @@
5.13 if attr is not None and attr.assignments == 1:
5.14 value = attr.get_value()
5.15
5.16 - if value is not obj and value in self.all_objects:
5.17 + # The value must have this object as a parent.
5.18 +
5.19 + if value is not obj and value.parent is obj and value in self.all_objects:
5.20 self.all_objects.remove(value)
5.21
5.22 # Delete class contents and lambdas from functions.
5.23 @@ -686,7 +688,7 @@
5.24 if self.in_loop:
5.25 print "Warning: class %r in %r defined in a loop." % (node.name, self.full_name())
5.26
5.27 - cls = Class(node.name, self.get_namespace(), self, node)
5.28 + cls = get_class(node.name, self.get_namespace(), self, node)
5.29
5.30 # Make a back reference from the node for code generation.
5.31
6.1 --- a/micropython/rsvp.py Thu Jan 13 23:58:12 2011 +0100
6.2 +++ b/micropython/rsvp.py Sat Jan 29 21:07:27 2011 +0100
6.3 @@ -3,7 +3,7 @@
6.4 """
6.5 RSVP instruction and serialisation classes.
6.6
6.7 -Copyright (C) 2007, 2008, 2009, 2010 Paul Boddie <paul@boddie.org.uk>
6.8 +Copyright (C) 2007, 2008, 2009, 2010, 2011 Paul Boddie <paul@boddie.org.uk>
6.9
6.10 This program is free software; you can redistribute it and/or modify it under
6.11 the terms of the GNU General Public License as published by the Free Software
6.12 @@ -20,7 +20,7 @@
6.13 """
6.14
6.15 from micropython.data import Attr, Const, Class, Function, Module
6.16 -from micropython.program import Block, DataObject, DataValue
6.17 +from micropython.program import Block, DataObject, DataValue, PlaceholderContext
6.18 from micropython.raw import RawObject
6.19
6.20 def name(attr):
6.21 @@ -82,7 +82,10 @@
6.22
6.23 def set_location(self, location, with_builtins):
6.24 self.item.instance_template_location = location
6.25 - return RSVPObject.set_location(self, location + 1, with_builtins)
6.26 +
6.27 + # Include the instance template and __class__ attribute in the size.
6.28 +
6.29 + return RSVPObject.set_location(self, location + 2, with_builtins)
6.30
6.31 def finalise_location(self, with_builtins):
6.32 self._finalise_location(with_builtins)
6.33 @@ -121,6 +124,13 @@
6.34 call_method_funccode # funccode
6.35 ),
6.36
6.37 + # The __class__ attribute for instances.
6.38 +
6.39 + DataValue(
6.40 + PlaceholderContext,
6.41 + item.location
6.42 + ),
6.43 +
6.44 # Class...
6.45
6.46 DataObject(
7.1 --- a/micropython/table.py Thu Jan 13 23:58:12 2011 +0100
7.2 +++ b/micropython/table.py Sat Jan 29 21:07:27 2011 +0100
7.3 @@ -175,10 +175,13 @@
7.4 if isinstance(attr, Class):
7.5 return (attr_index, None, None)
7.6
7.7 - if attr.parent is not None:
7.8 + # Get the absolute location for classes and modules.
7.9 +
7.10 + if attr.parent is not None and attr.is_static_attribute():
7.11 location = attr.parent.location or 0
7.12 else:
7.13 location = 0
7.14 +
7.15 if attr.position is not None:
7.16 position = attr.position + location + 1 # skip structure header
7.17 else:
8.1 --- a/micropython/trans.py Thu Jan 13 23:58:12 2011 +0100
8.2 +++ b/micropython/trans.py Sat Jan 29 21:07:27 2011 +0100
8.3 @@ -426,8 +426,10 @@
8.4
8.5 # Produce a suitable instruction.
8.6
8.7 - if AddressInstruction is not None:
8.8 + if attr.is_static_attribute() and AddressInstruction is not None:
8.9 self.replace_active_value(AddressInstruction(attr))
8.10 + elif not attr.is_static_attribute() and AttrInstruction is not None:
8.11 + self.replace_active_value(AttrInstruction(attr))
8.12 else:
8.13 raise TranslateError("Storing of class or module attribute %r via an object is not permitted." % attrname)
8.14
9.1 --- a/rsvp.py Thu Jan 13 23:58:12 2011 +0100
9.2 +++ b/rsvp.py Sat Jan 29 21:07:27 2011 +0100
9.3 @@ -5,7 +5,7 @@
9.4 ignore low-level operations and merely concentrate on variable access, structure
9.5 access, structure allocation and function invocations.
9.6
9.7 -Copyright (C) 2007, 2008, 2009, 2010 Paul Boddie <paul@boddie.org.uk>
9.8 +Copyright (C) 2007, 2008, 2009, 2010, 2011 Paul Boddie <paul@boddie.org.uk>
9.9
9.10 This program is free software; you can redistribute it and/or modify it under
9.11 the terms of the GNU General Public License as published by the Free Software
9.12 @@ -455,7 +455,7 @@
9.13 size = self.operand
9.14 value = self.value
9.15 # NOTE: Referencing the instance template.
9.16 - addr = self._MakeObject(size, value.ref - 1)
9.17 + addr = self._MakeObject(size, value.ref - 2)
9.18 # Introduce object as context for the new object.
9.19 self.value = DataValue(addr, addr)
9.20
9.21 @@ -817,6 +817,9 @@
9.22 addr = self.new(size)
9.23 # Save the header, overriding the size.
9.24 self.save(addr, data.with_size(size))
9.25 + # Copy the __class__ attribute.
9.26 + cls_attr = self.load(ref + 1)
9.27 + self.save(addr + 1, cls_attr)
9.28 return addr
9.29
9.30 def _MakeFragment(self, occupied, size):
11.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
11.2 +++ b/tests/class_class_attr.py Sat Jan 29 21:07:27 2011 +0100
11.3 @@ -0,0 +1,16 @@
11.4 +#!/usr/bin/env python
11.5 +
11.6 +class A:
11.7 + pass
11.8 +
11.9 +class B(A):
11.10 + pass
11.11 +
11.12 +class C:
11.13 + pass
11.14 +
11.15 +result_1 = A.__class__ is type and 1 or 0
11.16 +result_2 = B.__class__ is type and 2 or 0
11.17 +result_3 = C.__class__ is type and 3 or 0
11.18 +
11.19 +# vim: tabstop=4 expandtab shiftwidth=4
12.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
12.2 +++ b/tests/class_instance_attr.py Sat Jan 29 21:07:27 2011 +0100
12.3 @@ -0,0 +1,20 @@
12.4 +#!/usr/bin/env python
12.5 +
12.6 +class A:
12.7 + pass
12.8 +
12.9 +class B(A):
12.10 + pass
12.11 +
12.12 +class C:
12.13 + pass
12.14 +
12.15 +a = A()
12.16 +b = B()
12.17 +c = C()
12.18 +
12.19 +result_1 = a.__class__ is A and 1 or 0
12.20 +result_2 = b.__class__ is B and 2 or 0
12.21 +result_3 = c.__class__ is C and 3 or 0
12.22 +
12.23 +# vim: tabstop=4 expandtab shiftwidth=4
13.1 --- a/tests/subclass.py Thu Jan 13 23:58:12 2011 +0100
13.2 +++ b/tests/subclass.py Sat Jan 29 21:07:27 2011 +0100
13.3 @@ -5,18 +5,21 @@
13.4 self.x = x
13.5
13.6 def a(self):
13.7 - pass
13.8 + return self.x
13.9
13.10 class B(A):
13.11 def b(self):
13.12 - pass
13.13 + return 2
13.14
13.15 class C(A, B):
13.16 def a(self):
13.17 - A.a(self)
13.18 + return self.y
13.19
13.20 def b(self):
13.21 - self.a()
13.22 + return self.a()
13.23 +
13.24 + def c(self):
13.25 + return A.a(self)
13.26
13.27 def __init__(self, x, y):
13.28 self.x = x
13.29 @@ -24,7 +27,10 @@
13.30
13.31 class D:
13.32 def __init__(self, y):
13.33 - self.y = y
13.34 + self.z = y
13.35 +
13.36 + def a(self):
13.37 + return self.z
13.38
13.39 class E(C, D):
13.40 pass
13.41 @@ -32,4 +38,20 @@
13.42 class F(A, D):
13.43 pass
13.44
13.45 +a = A(1)
13.46 +b = B(1)
13.47 +c = C(1, 2)
13.48 +d = D(3)
13.49 +e = E(3, 4)
13.50 +f = F(5)
13.51 +
13.52 +result1_1 = a.a()
13.53 +result1_2 = b.b()
13.54 +result2_2 = c.a()
13.55 +result3_2 = c.b()
13.56 +result2_1 = c.c()
13.57 +result_3 = d.a()
13.58 +result_4 = e.a()
13.59 +result_5 = f.a()
13.60 +
13.61 # vim: tabstop=4 expandtab shiftwidth=4