# HG changeset patch # User Paul Boddie # Date 1203294145 -3600 # Node ID 136f6bf70d3fc9122ded82a369a565973e65c4e4 # Parent 7adb33e8b1d19969dcc395dbaeacca7352ebb669 Added a rationale for the project. Added elementary support for loops. Added the built-in types module to the importer's record of modules so that attribute information can be collected from that module. Added some instructions to support loops and some exception handling. diff -r 7adb33e8b1d1 -r 136f6bf70d3f docs/rationale.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/rationale.txt Mon Feb 18 01:22:25 2008 +0100 @@ -0,0 +1,71 @@ +Micropython: A minimal implementation of Python for constrained devices + + * Python provides a rich programming environment + * CPython enforces the "official" language version + * CPython, Jython, etc. expose the full strength language + +Motivations + + * Run Python programs in "small" devices + * Small programs + * Few executed instructions + +Python's flexibility comes at a cost + + * Modules, classes, instances are all objects + * Freedom to modify most objects at any time + * Difficult to predict eventual behaviour before execution + * Difficult to generate optimisations when compiling + +Attribute access + + * Must do a full lookup every time: + * Check instance dictionary + * Check class, superclasses + * Potentially lots of code + * Lots of executed instructions + +Other costly operations + + * Keyword parameter resolution + * Binary operators + * Comparisons + +Examples of rarely used/unnecessary flexibility + + * Can subclass dynamically: + + for cls in A, B: + class C(cls): + pass + + * Yet most people would relate to "class C" + * Not "the class currently known as C" + +Examples of occasionally used flexibility + + * Can evaluate classes and functions in dynamic contexts: + + if platform == 'posix': + class C: + ... + else: + class C: + ... + + * Seen quite often in the standard library with functions + * Dynamism used for configuration purposes + +Distinguish between "good" and "bad" dynamism + + * Dynamic class preparation + * Run-time choice of classes + * Assigning attributes to modules + +Run-time configuration + + * The source of a lot of "bad" dynamism + * Comparable to, but more robust than... + * Class loading tricks in Java + * Dynamic library loading magic in C/C++ + * Has a place, but perhaps not in compiled, embedded programs diff -r 7adb33e8b1d1 -r 136f6bf70d3f micropython/__init__.py --- a/micropython/__init__.py Sun Feb 17 02:38:04 2008 +0100 +++ b/micropython/__init__.py Mon Feb 18 01:22:25 2008 +0100 @@ -62,7 +62,7 @@ self.path = path or [os.getcwd()] self.verbose = verbose - self.modules = {} + self.modules = {"__builtins__" : micropython.inspect.builtins} self.loading = set() def vacuum(self): @@ -81,7 +81,7 @@ return self.modules.values() - def get_image(self, objtable=None, paramtable=None): + def get_image(self, objtable=None, paramtable=None, with_builtins=0): "Return a dictionary mapping modules to structures." @@ -91,6 +91,9 @@ image = [] for module_name, module in self.modules.items(): + if not with_builtins and module_name == "__builtins__": + continue + pos = len(image) # Position the module in the image and make a translation. @@ -155,7 +158,7 @@ # Append the function code to the image. - obj.code_location = obj.location = pos + obj.code_location = pos code = trans.get_code(obj) image += code pos += len(code) diff -r 7adb33e8b1d1 -r 136f6bf70d3f micropython/ast.py --- a/micropython/ast.py Sun Feb 17 02:38:04 2008 +0100 +++ b/micropython/ast.py Mon Feb 18 01:22:25 2008 +0100 @@ -78,7 +78,8 @@ self.unit = self.module self.code = [] - self.dispatch(self.module.module) + if self.module.module is not None: + self.dispatch(self.module.module) return self.code def get_code(self, unit): @@ -105,6 +106,9 @@ # Code writing methods. def new_label(self): + + "Return a new label object for use with set_label." + number = self.label_number label = Label(number) self.labels[label] = label @@ -121,12 +125,21 @@ label.location = len(self.code) + self.unit.code_location def new_op(self, op): + + "Add 'op' to the generated code." + self.code.append(op) def replace_op(self, op): + + "Replace the last added instruction with 'op'." + self.code[-1] = op def last_op(self): + + "Return the last added instruction." + return self.code[-1] # Visitor methods. @@ -138,9 +151,22 @@ return ASTVisitor.dispatch(self, node, *args) def _visitAttr(self, node, classes): - AttrInstruction, AttrIndexInstruction = classes + + """ + Visit the attribute-related 'node', generating instructions based on the + given 'classes'. + """ + self.dispatch(node.expr) + self._generateAttr(node.attrname, classes) + def _generateAttr(self, attrname, classes): + + """ + Generate code for the access to 'attrname' using the given 'classes'. + """ + + AttrInstruction, AttrIndexInstruction = classes # NOTE: Only simple cases are used for optimisations. last = self.last_op() @@ -151,17 +177,32 @@ target = last.attr.value target_name = target.full_name() table_entry = self.objtable.table[target_name] - pos = table_entry[node.attrname] + pos = table_entry[attrname] self.replace_op(AttrInstruction(pos)) else: - index = self.objtable.get_index(node.attrname) + index = self.objtable.get_index(attrname) self.new_op(AttrIndexInstruction(index)) def _visitName(self, node, classes): + + """ + Visit the name-related 'node', generating instructions based on the + given 'classes'. + """ + name = node.name scope = self.get_scope(name) #print self.module.name, node.lineno, name, scope + self._generateName(name, scope, classes, node) + + def _generateName(self, name, scope, classes, node): + + """ + Generate code for the access to 'name' in 'scope' using the given + 'classes', and using the given 'node' as the source of the access. + """ + NameInstruction, AttrInstruction = classes if scope == "local": @@ -245,6 +286,12 @@ self.dispatch(node.node) + # Generate the call. + + self._generateCallFunc(node.args, node) + + def _generateCallFunc(self, args, node): + # NOTE: Only simple cases are used for optimisations. last = self.last_op() @@ -257,10 +304,10 @@ positional = 1 - for i, arg in enumerate(node.args): + for i, arg in enumerate(args): if isinstance(arg, compiler.ast.Keyword): if positional: - self.new_op(ReserveFrame(len(node.args) - i)) + self.new_op(ReserveFrame(len(args) - i)) positional = 0 self.dispatch(arg.expr) @@ -322,7 +369,7 @@ def visitClass(self, node): unit = self.unit self.unit = node.unit - self.unit.code_location = self.module.code_location + len(self.code) + self.unit.code_location = self.module.code_location # class body code is not independently addressable self.dispatch(node.code) self.unit = unit @@ -364,7 +411,60 @@ def visitFloorDiv(self, node): pass - def visitFor(self, node): pass + def visitFor(self, node): + exit_label = self.new_label() + next_label = self.new_label() + else_label = self.new_label() + + # Get the "list" to be iterated over, obtain its iterator. + + self.dispatch(node.list) + self._generateAttr("__iter__", (LoadAttr, LoadAttrIndex)) + self._generateCallFunc([], node) + # Iterator on stack. + + # In the loop... + + self.set_label(next_label) + + # Use the iterator to get the next value. + + self.new_op(Duplicate()) + self._generateAttr("next", (LoadAttr, LoadAttrIndex)) + self._generateCallFunc([], node) + + # Test for StopIteration. + + self.new_op(CheckException("StopIteration")) # NOTE: To be done properly. + if node.else_ is not None: + self.new_op(JumpIfTrue(else_label)) + else: + self.new_op(JumpIfTrue(exit_label)) + + # Assign to the target. + + self.dispatch(node.assign) + + # Process the body with the current next and exit points. + + self.loop_labels.append((next_label, exit_label)) + self.dispatch(node.body) + self.loop_labels.pop() + + # Repeat the loop. + + self.new_op(Jump(next_label)) + + # Produce the "else" section. + + if node.else_ is not None: + self.set_label(exit_label) + self.dispatch(node.else_) + + # Pop the iterator. + + self.set_label(exit_label) + self.new_op(Pop()) def visitFrom(self, node): pass @@ -485,17 +585,23 @@ def visitWhile(self, node): exit_label = self.new_label() - self.dispatch(node.test) - self.new_op(JumpIfFalse(exit_label)) + next_label = self.new_label() + else_label = self.new_label() - next_label = self.new_label() self.set_label(next_label) + self.dispatch(node.test) + if node.else_ is not None: + self.new_op(JumpIfFalse(else_label)) + else: + self.new_op(JumpIfFalse(exit_label)) + self.loop_labels.append((next_label, exit_label)) self.dispatch(node.body) self.new_op(Jump(next_label)) if node.else_ is not None: + self.set_label(else_label) self.dispatch(node.else_) self.set_label(exit_label) diff -r 7adb33e8b1d1 -r 136f6bf70d3f micropython/inspect.py --- a/micropython/inspect.py Sun Feb 17 02:38:04 2008 +0100 +++ b/micropython/inspect.py Mon Feb 18 01:22:25 2008 +0100 @@ -959,6 +959,8 @@ def __init__(self): Module.__init__(self, "__builtins__") + self.loaded = 1 + self.module = None for key in ['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'DeprecationWarning', 'EOFError', 'Ellipsis', @@ -975,9 +977,20 @@ 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', 'basestring', 'bool', 'buffer', 'complex', 'dict', 'file', 'float', - 'frozenset', 'int', 'list', 'long', 'object', 'set', 'slice', 'str', + 'frozenset', 'int', # 'list', + 'long', 'object', 'set', 'slice', 'str', 'tuple', 'type', 'unicode', 'xrange']: - self[key] = Class(key, self.full_name(), self) + self.store(key, Class(key, self.full_name(), self)) + + # NOTE: Temporary measure - provide detailed built-ins. + + cls = Class("list", self.full_name(), self) + cls.set("__iter__", Function("__iter__", cls.full_name(), [], 0, 0, cls)) + self.store("list", cls) + + cls = Class("listiterator", self.full_name(), self) + cls.set("next", Function("next", cls.full_name(), [], 0, 0, cls)) + self.store("listiterator", cls) # NOTE: Incomplete: some functions have more than one parameter. @@ -991,6 +1004,13 @@ 'staticmethod', 'sum', 'super', 'unichr', 'vars', 'zip']: self[key] = Function(key, self.full_name(), ['arg'], 0, 0, self) + def store(self, name, obj): + self.set(name, obj) + self.all_objects.add(obj) + + def vacuum(self): + pass + builtins = Builtins() # vim: tabstop=4 expandtab shiftwidth=4 diff -r 7adb33e8b1d1 -r 136f6bf70d3f micropython/rsvp.py --- a/micropython/rsvp.py Sun Feb 17 02:38:04 2008 +0100 +++ b/micropython/rsvp.py Mon Feb 18 01:22:25 2008 +0100 @@ -35,11 +35,13 @@ # Instructions operating on the value stack. class LoadConst(Instruction): "Load the constant from the specified location." +class Duplicate(Instruction): "Duplicate the top of stack." +class Pop(Instruction): "Pop the top of stack." # Access within an invocation frame. -class LoadName(Instruction): "Load the object from the given local attribute." -class StoreName(Instruction): "Store the object in the given local attribute." +class LoadName(Instruction): "Load the object from the given local attribute/variable." +class StoreName(Instruction): "Store the object in the given local attribute/variable." # Access to address-relative data. @@ -62,7 +64,9 @@ class Jump(Instruction): pass class JumpIfFalse(Instruction): pass +class JumpIfTrue(Instruction): pass class LoadCallable(Instruction): pass class Return(Instruction): pass +class CheckException(Instruction): pass # vim: tabstop=4 expandtab shiftwidth=4