# HG changeset patch # User Paul Boddie # Date 1238893559 -7200 # Node ID 072c14d15074a2fd11efade435bf45ef490e512a # Parent 59637ebeb668941c21ac1b6cbfe78b63449437b3 Moved helper and administrative Translation methods into the trans module's Helper class. Fixed code generation for accessing constants such as True and False. Added true and false value addresses to the RSVP machine. Added a test involving a swap operation, which should be optimised along with sequence assignment in general. diff -r 59637ebeb668 -r 072c14d15074 micropython/__init__.py --- a/micropython/__init__.py Fri Mar 20 00:32:43 2009 +0100 +++ b/micropython/__init__.py Sun Apr 05 03:05:59 2009 +0200 @@ -412,7 +412,7 @@ # Name records (used to track actual use of names). # Include names which may not be explicitly used in programs. - self.names_used = set(["__init__", "__call__"]) + self.names_used = set(["__init__", "__call__", "__getitem__", "__bool__"]) # Status information. diff -r 59637ebeb668 -r 072c14d15074 micropython/ast.py --- a/micropython/ast.py Fri Mar 20 00:32:43 2009 +0100 +++ b/micropython/ast.py Sun Apr 05 03:05:59 2009 +0200 @@ -23,12 +23,13 @@ from micropython.common import * from micropython.data import * from micropython.rsvp import * +from micropython.trans import Helper import compiler.ast from compiler.visitor import ASTVisitor # Program visitors. -class Translation(ASTVisitor): +class Translation(ASTVisitor, Helper): "A translated module." @@ -195,265 +196,6 @@ self.unit.blocks = self.blocks return self.blocks - # Allocation-related methods. - - def make_object(self, cls, n): - - """ - Request a new object with the given class 'cls' and with 'n' attributes. - """ - - # Load the class in order to locate the instance template. - - self.new_op(LoadConst(cls)) - - # NOTE: Object headers are one location. - - self.new_op(MakeObject(n + 1)) - - # Name-related methods. - - def get_scope(self, name): - - "Return the scope for the given 'name'." - - if self.unit.has_key(name): - return "local" - elif self.module.has_key(name): - return "global" - else: - return "builtins" - - def load_builtin(self, name, node): - - "Generate an instruction loading 'name' for the given 'node'." - - self.new_op(LoadAddress(self.get_builtin(name, node))) - - def get_builtin_class(self, name, node): - - "Return the built-in class with the given 'name' for the given 'node'." - - return self.get_builtin(name, node).get_value() - - def get_builtin(self, name, node): - - """ - Return the built-in module definition for the given 'name', used by the - given 'node'. - """ - - if self.builtins is not None: - try: - return self.builtins[name] - except KeyError: - raise TranslateError(self.module.full_name(), node, "No __builtins__ definition is available for name %r." % name) - else: - raise TranslateError(self.module.full_name(), node, "No __builtins__ module is available for name %r." % name) - - # Code feature methods. - - def new_block(self): - - "Return a new code block." - - return Block() - - def set_block(self, block): - - "Add the given 'block' to the unit's list of blocks." - - self.optimiser.reset() - self.blocks.append(block) - - def get_loop_blocks(self): - return self.loop_blocks[-1] - - def add_loop_blocks(self, next_block, exit_block): - self.loop_blocks.append((next_block, exit_block)) - - def drop_loop_blocks(self): - self.loop_blocks.pop() - - def get_exception_blocks(self): - return self.exception_blocks[-1] - - def add_exception_blocks(self, handler_block, exit_block): - self.exception_blocks.append((handler_block, exit_block)) - - def drop_exception_blocks(self): - self.exception_blocks.pop() - - # Assignment expression values. - - def record_value(self, immediate=1): - - """ - Record the current active value for an assignment. If the optional - 'immediate' parameter if set to a false value always allocates new - temporary storage to hold the recorded value; otherwise, the - value-providing instruction may be replicated in order to provide the - active value later on. - """ - - if immediate: - temp = self.optimiser.optimise_temp_storage() - else: - temp = self.get_temp() - self.expr_temp.append(temp) - - def discard_value(self): - - "Discard any temporary storage in use for the current assignment value." - - self.discard_temp(self.expr_temp.pop()) - - def set_source(self): - - "Set the source of an assignment using the current assignment value." - - self.optimiser.set_source(self.expr_temp[-1]) - - # Optimise away constant storage if appropriate. - - if self.optimiser.optimise_constant_storage(): - self.remove_op() - - def is_immediate_user(self, node): - - """ - Return whether 'node' is an immediate user of an assignment expression. - """ - - return isinstance(node, (compiler.ast.AssName, compiler.ast.AssAttr)) - - def has_immediate_usage(self, nodes): - - """ - Return whether 'nodes' are all immediate users of an assignment expression. - """ - - for n in nodes: - if not self.is_immediate_user(n): - return 0 - return 1 - - # Temporary storage administration. - - def get_temp(self): - - """ - Add a temporary storage instruction for the current value and return a - sequence of access instructions. - """ - - position_in_frame = self.reserve_temp() - self.new_op(StoreTemp(position_in_frame)) - return LoadTemp(position_in_frame) - - def reserve_temp(self, temp_position=None): - - """ - Reserve a new temporary storage position, or if the optional - 'temp_position' is specified, ensure that this particular position is - reserved. - """ - - if temp_position is not None: - pass - elif not self.temp_positions: - temp_position = 0 - else: - temp_position = max(self.temp_positions) + 1 - self.temp_positions.add(temp_position) - self.max_temp_position = max(self.max_temp_position, temp_position) - return self.unit.all_local_usage + temp_position # position in frame - - def ensure_temp(self, instruction=None): - - """ - Ensure that the 'instruction' is using a reserved temporary storage - position. - """ - - if isinstance(instruction, LoadTemp): - temp_position = instruction.attr - self.unit.all_local_usage - self.reserve_temp(temp_position) - - def discard_temp(self, instruction=None): - - "Discard any temporary storage position used by 'instruction'." - - if isinstance(instruction, LoadTemp): - temp_position = instruction.attr - self.unit.all_local_usage - self.free_temp(temp_position) - - def free_temp(self, temp_position): - - "Free the temporary storage position specified by 'temp_position'." - - if temp_position in self.temp_positions: - self.temp_positions.remove(temp_position) - - def set_frame_usage(self, node, extend): - - """ - Ensure that the frame usage for the unit associated with 'node' is set - on the 'extend' instruction. - """ - - ntemp = self.max_temp_position + 1 - extend.attr = ntemp + node.unit.local_usage # NOTE: See get_code for similar code. - - # Code writing methods. - - def new_op(self, op): - - "Add 'op' to the generated code." - - # Optimise load operations employed by this instruction. - - self.optimiser.optimise_load_operations(op) - if self.optimiser.optimise_away_no_operations(op): - return - - # Add the operation to the current block. - - self.blocks[-1].code.append(op) - self.optimiser.set_new(op) - - def remove_op(self): - - "Remove the last instruction." - - op = self.blocks[-1].code.pop() - self.optimiser.clear_active() - - def replace_op(self, op): - - "Replace the last added instruction with 'op'." - - self.remove_op() - self.new_op(op) - - def replace_active_value(self, op): - - """ - Replace the value-providing active instruction with 'op' if appropriate. - """ - - self.optimiser.remove_active_value() - self.new_op(op) - - def last_op(self): - - "Return the last added instruction." - - try: - return self.blocks[-1].code[-1] - except IndexError: - return None - # Visitor methods. def default(self, node, *args): @@ -462,882 +204,28 @@ def dispatch(self, node, *args): return ASTVisitor.dispatch(self, node, *args) - # Internal helper methods. - - def _visitAttr(self, node, classes): - - """ - Visit the attribute-related 'node', generating instructions based on the - given 'classes'. - """ - - self.dispatch(node.expr) - self._generateAttr(node, node.attrname, classes) - - def _generateAttr(self, node, attrname, classes): - - """ - Generate code for the access to 'attrname' using the given 'classes'. - """ - - AddressInstruction, AddressContextInstruction, AddressContextCondInstruction, \ - AttrInstruction, AttrIndexInstruction, AttrIndexContextInstruction, \ - AttrIndexContextCondInstruction = classes - - # Where the last operation (defining the attribute owner) yields a - # constant... - - target_name = self.optimiser.optimise_constant_accessor() - - # Only try and discover the position if the target can be resolved. - # Since instances cannot be constants, this involves classes and - # modules. - - if target_name is not None: - - # Access the object table to get the attribute position. - - try: - table_entry = self.objtable.table[target_name] - except KeyError: - raise TranslateError(self.module.full_name(), node, - "No object entry exists for target %r." % target_name) - - try: - pos = table_entry[attrname] - except KeyError: - raise TranslateError(self.module.full_name(), node, - "No attribute entry exists for name %r in target %r." % (attrname, target_name)) - - # Produce a suitable instruction. - - if AddressInstruction is not None: - self.replace_active_value(AddressInstruction(pos)) - else: - raise TranslateError(self.module.full_name(), node, - "Storing of class or module attribute %r via an object is not permitted." % attrname) - - return - - # Where the last operation involves the special 'self' name, check to - # see if the attribute is acceptably positioned and produce a direct - # access to the attribute. - - # This is the only reliable way of detecting instance accesses at - # compile-time since in general, objects could be classes or modules, - # but 'self' should only refer to instances. - - elif self.optimiser.optimise_self_access(self.unit, attrname): - - # Either generate an instruction operating on an instance attribute. - - try: - attr = self.unit.parent.instance_attributes()[attrname] - self.new_op(AttrInstruction(attr)) - return - - # Or generate an instruction operating on a class attribute. - # NOTE: Any simple instruction providing self is not removed. - - except KeyError: - - try: - attr = self.unit.parent.all_attributes()[attrname] - - # Switch the context if the class attribute is compatible with - # the instance. - - if attr.defined_within_hierarchy(): - - # Only permit loading (not storing) of class attributes via self. - - if AddressContextInstruction is not None: - self.new_op(AddressContextInstruction(attr)) - else: - raise TranslateError(self.module.full_name(), node, - "Storing of class attribute %r via self not permitted." % attrname) - - # Preserve the context if the class attribute comes from an - # incompatible class. - - elif attr.defined_outside_hierarchy(): - - # Only permit loading (not storing) of class attributes via self. - - if AddressInstruction is not None: - self.new_op(AddressInstruction(attr)) - else: - raise TranslateError(self.module.full_name(), node, - "Storing of class attribute %r via self not permitted." % attrname) - - # Otherwise, test for a suitable context at run-time. - - else: - - # Only permit loading (not storing) of class attributes via self. - - if AddressContextCondInstruction is not None: - self.new_op(AddressContextCondInstruction(attr)) - else: - raise TranslateError(self.module.full_name(), node, - "Storing of class attribute %r via self not permitted." % attrname) - - return - - # Or delegate the attribute access to a general instruction - # since the kind of attribute cannot be deduced. - - except KeyError: - pass - - # Otherwise, perform a normal operation. - - try: - index = self.objtable.get_index(attrname) - - except self.objtable.TableError: - raise TranslateError(self.module.full_name(), node, - "No attribute entry exists for name %r." % attrname) - - # NOTE: Test for class vs. instance attributes, generating - # NOTE: context-related instructions. - - if AttrIndexContextCondInstruction is not None: - self.new_op(AttrIndexContextCondInstruction(index)) - - # Store instructions do not need to consider context modifications. - - else: - self.new_op(AttrIndexInstruction(index)) - - # Invocations involve the following: - # - # 1. Reservation of a frame for the arguments - # 2. Identification of the target which is then held in temporary storage - # 3. Optional inclusion of a context (important for methods) - # 4. Preparation of the argument frame - # 5. Invocation of the target - # 6. Discarding of the frame - # - # In order to support nested invocations - such as a(b(c)) - use of the - # temporary storage is essential. - - def _startCallFunc(self): - - "Record the location of the invocation." - - op = MakeFrame() - self.new_op(op) # records the start of the frame - self.frame_makers.append(op) - - def _generateCallFunc(self, args, node): - - """ - Support a generic function invocation using the given 'args', occurring - on the given 'node', where the expression providing the invocation - target has just been generated. - - In other situations, the invocation is much simpler and does not need to - handle the full flexibility of a typical Python invocation. Internal - invocations, such as those employed by operators and certain - control-flow mechanisms, use predetermined arguments and arguably do not - need to support the same things as the more general invocations. - """ - - target, context, temp = self._generateCallFuncContext() - self._generateCallFuncArgs(target, context, temp, args, node) - return temp, target - - def _generateCallFuncContext(self): - - """ - Produce code which loads and checks the context of the current - invocation, the instructions for whose target have already been - produced, returning a list of instructions which reference the - invocation target. - """ - - t = self.optimiser.optimise_known_target() - if t: - target, context = t - if isinstance(target, Instance): # lambda object - target, context = None, None - else: - target, context = None, None - - # Store the target in temporary storage for subsequent referencing. - # NOTE: This may not be appropriate for class invocations - # NOTE: (instantiation). - - temp = self.optimiser.optimise_temp_storage() - - # Where a target or context are not known or where an instance is known - # to be the context, load the context. - - if target is None or isinstance(context, Instance): - self.new_op(temp) - self.new_op(LoadContext()) - self.new_op(StoreFrame(0)) - - # For known instantiations, provide a new object as the first argument - # to the __init__ method. - - elif isinstance(target, Class): - self.make_object(target, len(target.instance_attributes())) - self.new_op(StoreFrame(0)) - - # Otherwise omit the context. - - else: - pass # NOTE: Class methods should be supported. - - return target, context, temp - - def _generateCallFuncArgs(self, target, context, temp, args, node): - - """ - Given invocation 'target' and 'context' information, the 'temp' - reference to the target, a list of nodes representing the 'args' - (arguments), generate instructions which load the arguments for the - invocation defined by the given 'node'. - """ - - # Evaluate the arguments. - - employed_positions = set() - employed_keywords = set() - extra_keywords = [] - - # Find keyword arguments in advance in order to help resolve targets. - - for arg in args: - if isinstance(arg, compiler.ast.Keyword): - employed_keywords.add(arg.name) - - possible_targets = self.paramtable.all_possible_objects(employed_keywords) - - # Note the presence of the context in the frame where appropriate. - - if target is None or isinstance(context, Instance): - ncontext = 1 - expect_context = 0 - - # Handle calls to classes. - - elif isinstance(target, Class): - ncontext = 1 - expect_context = 0 - target = target.get_init_method() - - # Method calls via classes. - - elif isinstance(context, Class): - ncontext = 0 - expect_context = 1 - - # Function calls. - - else: - ncontext = 0 - expect_context = 0 - - first = 1 - frame_pos = ncontext - max_keyword_pos = -1 - - for arg in args: - - # Handle positional and keyword arguments separately. - - if isinstance(arg, compiler.ast.Keyword): - - # Optimise where the target is known now. - - if target is not None: - - # Find the parameter table entry for the target. - - target_name = target.full_name() - - # Look for a callable with the precise target name. - - table_entry = self.paramtable.table[target_name] - - # Look the name up in the parameter table entry. - - try: - pos = table_entry[arg.name] - - # Where no position is found, this could be an extra keyword - # argument. - - except KeyError: - extra_keywords.append(arg) - continue - - # Test for illegal conditions. - - if pos in employed_positions: - raise TranslateError(self.module.full_name(), node, - "Keyword argument %r overwrites parameter %r." % (arg.name, pos)) - - employed_positions.add(pos) - - # Generate code for the keyword and the positioning - # operation. - - self.dispatch(arg.expr) - self.new_op(StoreFrame(pos)) - - # Otherwise, generate the code needed to obtain the details of - # the parameter location. - - else: - - # Combine the target details with the name to get the location. - # See the access method on the List class. - - try: - paramindex = self.paramtable.get_index(arg.name) - - # Where no position is found, this could be an extra keyword - # argument. - - except self.paramtable.TableError: - extra_keywords.append(arg) - continue - - # Generate code for the keyword and the positioning - # operation. Get the value as the source of the assignment. - - self.dispatch(arg.expr) - self.record_value() - - # Store the source value using the callable's parameter - # table information. - - self.new_op(temp) - self.new_op(StoreFrameIndex(paramindex)) - - self.set_source() - self.discard_value() - - # Record the highest possible frame position for this argument. - - max_keyword_pos = max(max_keyword_pos, max(self.paramtable.all_attribute_positions(arg.name))) - - else: - self.dispatch(arg) - self.new_op(StoreFrame(frame_pos)) - - employed_positions.add(frame_pos) - - # Check to see if the first argument is appropriate (compatible with - # the target where methods are being invoked via classes). - - if first and expect_context: - - # Drop any test if the target and the context are known. - - if not self.optimiser.have_correct_self_for_target(context, self.unit): - - continue_block = self.new_block() - - self.new_op(CheckSelf()) - self.optimiser.set_source(temp) - self.new_op(JumpIfTrue(continue_block)) - - # Where the context is inappropriate, drop the incomplete frame and - # raise an exception. - - self.new_op(DropFrame()) - self.new_op(LoadResult()) - - self.load_builtin("TypeError", node) - self.new_op(StoreException()) - self.new_op(RaiseException()) - - self.set_block(continue_block) - - first = 0 - frame_pos += 1 - - # NOTE: Extra keywords are not supported. - # NOTE: Somehow, the above needs to be combined with * arguments. - - if extra_keywords: - print "Warning: extra keyword argument(s) %s not handled." % ", ".join([arg.name for arg in extra_keywords]) - - # Either test for a complete set of arguments. - - if target is not None: - - # Make sure that enough arguments have been given. - - nargs_max = len(target.positional_names) - ndefaults = len(target.defaults) - nargs_min = nargs_max - ndefaults - - for i in range(ncontext, nargs_min): - if i not in employed_positions: - raise TranslateError(self.module.full_name(), node, - "Argument %r not supplied for %r: need at least %d argument(s)." % (i+1, target.name, nargs_min)) - - nargs = frame_pos - - if nargs > nargs_max and not target.has_star and not target.has_dstar: - raise TranslateError(self.module.full_name(), node, - "Too many arguments for %r: need at most %d argument(s)." % (target.name, nargs_max)) - - # Where defaults are involved, put them into the frame. - - self._generateCallFuncDefaultArgs(target, temp, nargs_min, nargs_max, employed_positions) - - # Set the frame size. - - self._endCallFuncArgs(nargs_max) - - # Or generate instructions to do this at run-time. - - else: - max_pos = max(max(employed_positions or [-1]), max_keyword_pos, frame_pos - 1) - - # Only check non-empty frames (using the callable's details). - - if employed_positions or max_pos >= 0: - self.new_op(temp) - self.new_op(CheckFrame(max_pos + 1)) - - # Set the frame size. - - self._endCallFuncArgs(max_pos + 1) - - def _generateCallFuncDefaultArgs(self, target, temp, nargs_min, nargs_max, employed_positions): - - """ - For the given 'target' and 'temp' reference to the target, generate - default arguments for those positions in the range 'nargs_min'... - 'nargs_max' which are not present in the 'employed_positions' - collection. - """ - - # Where a lambda is involved, construct a dynamic object to hold the - # defaults. - - dynamic = target.name is None - - # Here, we use negative index values to visit the right hand end of - # the defaults list. - - for pos in range(nargs_min, nargs_max): - if pos not in employed_positions: - if dynamic: - self.new_op(temp) - self.new_op(LoadAttr(target.default_attrs[pos - nargs_min])) - else: - self.new_op(LoadAddress(target.default_attrs[pos - nargs_min])) - self.new_op(StoreFrame(pos)) - - def _doCallFunc(self, instruction, target=None): - - "Make the invocation." - - if isinstance(target, Class): - self.new_op(LoadConst(target.get_init_method())) - else: - self.new_op(instruction) - self.new_op(LoadCallable()) - self.new_op(JumpWithFrame()) - - def _endCallFuncArgs(self, nargs): - - "Set the frame size." - - self.frame_makers[-1].attr = nargs - self.frame_makers.pop() - - def _endCallFunc(self, instruction=None, target=None, load_result=1): - - "Finish the invocation and tidy up afterwards." - - if isinstance(target, Class): - self.new_op(LoadName(target.get_init_method().all_locals()["self"])) # load the context in the invocation frame - self.new_op(StoreResult()) - self.new_op(DropFrame()) - if load_result: - self.new_op(LoadResult()) - - # Discard any temporary storage instructions. - - if instruction is not None: - self.discard_temp(instruction) - - def _generateFunctionDefaults(self, function): - - """ - Generate the default initialisation code for 'function', returning - a temporary storage reference if a dynamic object was created for the - function. - """ - - attr_to_default = zip(function.default_attrs, function.defaults) - if not attr_to_default: - return None - - # Where a lambda is involved, construct a dynamic object to hold the - # defaults. - - dynamic = function.name is None - - if dynamic: - self.make_object(self.get_builtin_class("function", function), len(attr_to_default)) - temp = self.get_temp() - - for attr, default in attr_to_default: - self.dispatch(default) - - self.record_value() - if dynamic: - self.new_op(temp) - self.new_op(StoreAttr(attr)) - else: - self.new_op(StoreAddress(attr)) - self.set_source() - self.discard_value() - - if dynamic: - return temp - else: - return None - - 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, AddressInstruction = classes - - if scope == "local": - unit = self.unit - if isinstance(unit, Function): - self.new_op(NameInstruction(unit.all_locals()[name])) - elif isinstance(unit, Class): - self.new_op(AddressInstruction(unit.all_class_attributes()[name])) - elif isinstance(unit, Module): - self.new_op(AddressInstruction(unit.module_attributes()[name])) - else: - raise TranslateError(self.module.full_name(), node, "Program unit %r has no local %r." % (unit, name)) - - elif scope == "global": - globals = self.module.module_attributes() - if globals.has_key(name): - self.new_op(AddressInstruction(globals[name])) - else: - raise TranslateError(self.module.full_name(), node, "Module %r has no attribute %r." % (self.module, name)) - - else: - self.new_op(AddressInstruction(self.get_builtin(name, node))) - - def _visitUnary(self, node): - - """ - _t = node.expr - try: - _result = _t.__pos__() - except AttributeError: - raise TypeError - """ - - method = unary_methods[node.__class__.__name__] - - type_error_block = self.new_block() - end_block = self.new_block() - - # Evaluate and store the operand in temporary storage. - - self.dispatch(node.expr) - temp = self.optimiser.optimise_temp_storage() - - self.new_op(temp) - - # Get the method on temp. - - self._generateAttr(node, method, self.attribute_load_instructions) - temp_method = self.optimiser.optimise_temp_storage() - - self._handleAttributeError(node, type_error_block) - - # Add arguments. - # NOTE: No support for defaults. - - self._startCallFunc() - self.new_op(temp) # Explicit context as first argument. - self.new_op(StoreFrame(0)) - self._endCallFuncArgs(1) - self._doCallFunc(temp_method) - self._endCallFunc(temp_method) - self.new_op(Jump(end_block)) - - # Store the result. - - temp_out = self.get_temp() - - # Raise a TypeError. - - self.set_block(type_error_block) - self.load_builtin("TypeError", node) - self.new_op(StoreException()) - self.new_op(RaiseException()) - - self.set_block(end_block) - - # Produce the result. - - self.new_op(temp_out) - - # Compilation duties... - - self.discard_temp(temp) - self.discard_temp(temp_out) - - def _visitBinary(self, node): - - """ - _t1 = node.left - _t2 = node.right - try: - _result = _t1.__add__(_t2) - if _result is NotImplemented: - raise AttributeError - except AttributeError: - try: - _result = _t2.__radd__(_t1) - if _result is NotImplemented: - raise AttributeError - except AttributeError: - raise TypeError - """ - - left_method, right_method = binary_methods[node.__class__.__name__] - - # Evaluate and store the left operand in temporary storage. - - self.dispatch(node.left) - temp1 = self.optimiser.optimise_temp_storage() - - # Evaluate and store the right operand in temporary storage. - - self.dispatch(node.right) - temp2 = self.optimiser.optimise_temp_storage() - - temp_out = self._generateBinary(node, temp1, temp2, left_method, right_method) - - # Produce the result. - - self.new_op(temp_out) - - # Compilation duties... - - self.discard_temp(temp1) - self.discard_temp(temp2) - self.discard_temp(temp_out) - - def _generateBinary(self, node, temp1, temp2, left_method, right_method): - - """ - For the given 'node', generate the binary operator pattern for the - operands 'temp1' and 'temp2', employing 'left_method' and 'right_method' - as defined for binary operators, but also used in comparisons (for which - this method is provided). - - A temporary storage reference is returned from this method. - """ - - right_block = self.new_block() - type_error_block = self.new_block() - end_block = self.new_block() - - # Left method. - - temp_out = self._generateOpMethod(node, temp1, temp2, left_method, right_block, end_block) - self.discard_temp(temp_out) # NOTE: Will re-use the same storage. - - # Right method. - - self.set_block(right_block) - temp_out = self._generateOpMethod(node, temp2, temp1, right_method, type_error_block, end_block) - - # Raise a TypeError. - - self.set_block(type_error_block) - self.load_builtin("TypeError", node) - self.new_op(StoreException()) - self.new_op(RaiseException()) - - self.set_block(end_block) - return temp_out - - def _generateOpMethod(self, node, temp1, temp2, method_name, next_method_block, end_block): - - """ - For the given 'node', generate the operator method invocation using the - operands 'temp1' and 'temp2', employing the given 'method_name', and - jumping appropriately to 'next_method_block' where a NotImplemented - result is returned, or to 'end_block' if the method call was successful. - - A temporary storage reference is returned from this method. - """ - - end_attempt_block = self.new_block() - - self.new_op(temp1) - - # Get method on temp1. - - self._generateAttr(node, method_name, self.attribute_load_instructions) - temp_method = self.optimiser.optimise_temp_storage() - - self._handleAttributeError(node, end_attempt_block) - - # Add arguments. - # NOTE: No support for defaults. - - self._startCallFunc() - self.new_op(temp1) - self.new_op(StoreFrame(0)) - self.new_op(temp2) - self.new_op(StoreFrame(1)) - self._endCallFuncArgs(2) - self._doCallFunc(temp_method) - self._endCallFunc(temp_method) - - # Store the result. - - temp_out = self.get_temp() - - # Test for NotImplemented. - # Don't actually raise an exception. - - self.new_op(TestIdentityAddress(self.importer.get_predefined_constant("NotImplemented"))) - self.new_op(JumpIfTrue(next_method_block)) - self.new_op(Jump(end_block)) - - # End method attempt. - - self.set_block(end_attempt_block) - return temp_out - - def _handleAttributeError(self, node, end_call_block): - - """ - Add exception handling to the method acquisition instructions where the - attribute access cannot be resolved at compile-time. - """ - - if not self.optimiser.optimise_known_target(): - self.load_builtin("AttributeError", node) - self.new_op(CheckException()) - self.new_op(JumpIfTrue(end_call_block)) - - def _generateSequence(self, sequence_type, node): - - "Make a sequence of 'sequence_type' for the given program 'node'." - - self.make_object(self.get_builtin_class(sequence_type, node), len(node.nodes)) - temp = self.get_temp() - - for i, n in enumerate(node.nodes): - self.dispatch(n) - self.record_value() - self.new_op(temp) - self.new_op(StoreAttr(Attr(i, None, None))) - self.set_source() - self.discard_value() - - self.new_op(temp) - self.discard_temp(temp) - - def _generateTestBoolean(self, node, temp): - - """ - Generate a test of the boolean status of the current value for the given - program 'node'. - """ - - # Get method on temp. - # NOTE: Using __bool__ instead of __nonzero__. - - self._generateAttr(node, "__bool__", self.attribute_load_instructions) - temp_method = self.optimiser.optimise_temp_storage() - - self._startCallFunc() - self.new_op(temp) - self.new_op(StoreFrame(0)) - self._endCallFuncArgs(1) - self._doCallFunc(temp_method) - self._endCallFunc(temp_method) - - self.discard_temp(temp_method) - - # Convert result to boolean (a StoreBoolean operation). - - self.new_op(TestIdentityAddress(self.get_builtin("True", node))) - - def _generateLoadBoolean(self, node): - - """ - Generate instructions to load the appropriate value given the current - boolean status. - """ - - true_block = self.new_block() - end_block = self.new_block() - - self.new_op(JumpIfTrue(true_block)) - self.load_builtin("False", node) - self.new_op(Jump(end_block)) - - self.set_block(true_block) - self.load_builtin("True", node) - - self.set_block(end_block) - # Concrete visitor methods. # Binary operators. - visitAdd = _visitBinary - visitBitand = _visitBinary - visitBitor = _visitBinary - visitBitxor = _visitBinary - visitDiv = _visitBinary - visitFloorDiv = _visitBinary - visitLeftShift = _visitBinary - visitMod = _visitBinary - visitMul = _visitBinary - visitPower = _visitBinary - visitRightShift = _visitBinary - visitSub = _visitBinary + visitAdd = Helper._visitBinary + visitBitand = Helper._visitBinary + visitBitor = Helper._visitBinary + visitBitxor = Helper._visitBinary + visitDiv = Helper._visitBinary + visitFloorDiv = Helper._visitBinary + visitLeftShift = Helper._visitBinary + visitMod = Helper._visitBinary + visitMul = Helper._visitBinary + visitPower = Helper._visitBinary + visitRightShift = Helper._visitBinary + visitSub = Helper._visitBinary # Unary operators. - visitInvert = _visitUnary - visitUnaryAdd = _visitUnary - visitUnarySub = _visitUnary + visitInvert = Helper._visitUnary + visitUnaryAdd = Helper._visitUnary + visitUnarySub = Helper._visitUnary # Logical operators. diff -r 59637ebeb668 -r 072c14d15074 micropython/trans.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/micropython/trans.py Sun Apr 05 03:05:59 2009 +0200 @@ -0,0 +1,1148 @@ +#!/usr/bin/env python + +""" +Translate the AST of a Python program into a more interpretable representation. + +Copyright (C) 2007, 2008, 2009 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 +Foundation; either version 3 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see . +""" + +from micropython.common import * +from micropython.data import * +from micropython.rsvp import * +import compiler.ast + +class Helper: + + "Internal helper methods for AST visitors." + + # Allocation-related methods. + + def make_object(self, cls, n): + + """ + Request a new object with the given class 'cls' and with 'n' attributes. + """ + + # Load the class in order to locate the instance template. + + self.new_op(LoadConst(cls)) + + # NOTE: Object headers are one location. + + self.new_op(MakeObject(n + 1)) + + # Name-related methods. + + def get_scope(self, name): + + "Return the scope for the given 'name'." + + if self.unit.has_key(name): + return "local" + elif self.module.has_key(name): + return "global" + else: + return "builtins" + + def load_builtin(self, name, node): + + "Generate an instruction loading 'name' for the given 'node'." + + self.new_op(LoadAddress(self.get_builtin(name, node))) + + def get_builtin_class(self, name, node): + + "Return the built-in class with the given 'name' for the given 'node'." + + return self.get_builtin(name, node).get_value() + + def get_builtin(self, name, node): + + """ + Return the built-in module definition for the given 'name', used by the + given 'node'. + """ + + if self.builtins is not None: + try: + return self.builtins[name] + except KeyError: + raise TranslateError(self.module.full_name(), node, "No __builtins__ definition is available for name %r." % name) + else: + raise TranslateError(self.module.full_name(), node, "No __builtins__ module is available for name %r." % name) + + # Code feature methods. + + def new_block(self): + + "Return a new code block." + + return Block() + + def set_block(self, block): + + "Add the given 'block' to the unit's list of blocks." + + self.optimiser.reset() + self.blocks.append(block) + + def get_loop_blocks(self): + return self.loop_blocks[-1] + + def add_loop_blocks(self, next_block, exit_block): + self.loop_blocks.append((next_block, exit_block)) + + def drop_loop_blocks(self): + self.loop_blocks.pop() + + def get_exception_blocks(self): + return self.exception_blocks[-1] + + def add_exception_blocks(self, handler_block, exit_block): + self.exception_blocks.append((handler_block, exit_block)) + + def drop_exception_blocks(self): + self.exception_blocks.pop() + + # Assignment expression values. + + def record_value(self, immediate=1): + + """ + Record the current active value for an assignment. If the optional + 'immediate' parameter if set to a false value always allocates new + temporary storage to hold the recorded value; otherwise, the + value-providing instruction may be replicated in order to provide the + active value later on. + """ + + if immediate: + temp = self.optimiser.optimise_temp_storage() + else: + temp = self.get_temp() + self.expr_temp.append(temp) + + def discard_value(self): + + "Discard any temporary storage in use for the current assignment value." + + self.discard_temp(self.expr_temp.pop()) + + def set_source(self): + + "Set the source of an assignment using the current assignment value." + + self.optimiser.set_source(self.expr_temp[-1]) + + # Optimise away constant storage if appropriate. + + if self.optimiser.optimise_constant_storage(): + self.remove_op() + + def is_immediate_user(self, node): + + """ + Return whether 'node' is an immediate user of an assignment expression. + """ + + return isinstance(node, (compiler.ast.AssName, compiler.ast.AssAttr)) + + def has_immediate_usage(self, nodes): + + """ + Return whether 'nodes' are all immediate users of an assignment expression. + """ + + for n in nodes: + if not self.is_immediate_user(n): + return 0 + return 1 + + # Temporary storage administration. + + def get_temp(self): + + """ + Add a temporary storage instruction for the current value and return a + sequence of access instructions. + """ + + position_in_frame = self.reserve_temp() + self.new_op(StoreTemp(position_in_frame)) + return LoadTemp(position_in_frame) + + def reserve_temp(self, temp_position=None): + + """ + Reserve a new temporary storage position, or if the optional + 'temp_position' is specified, ensure that this particular position is + reserved. + """ + + if temp_position is not None: + pass + elif not self.temp_positions: + temp_position = 0 + else: + temp_position = max(self.temp_positions) + 1 + self.temp_positions.add(temp_position) + self.max_temp_position = max(self.max_temp_position, temp_position) + return self.unit.all_local_usage + temp_position # position in frame + + def ensure_temp(self, instruction=None): + + """ + Ensure that the 'instruction' is using a reserved temporary storage + position. + """ + + if isinstance(instruction, LoadTemp): + temp_position = instruction.attr - self.unit.all_local_usage + self.reserve_temp(temp_position) + + def discard_temp(self, instruction=None): + + "Discard any temporary storage position used by 'instruction'." + + if isinstance(instruction, LoadTemp): + temp_position = instruction.attr - self.unit.all_local_usage + self.free_temp(temp_position) + + def free_temp(self, temp_position): + + "Free the temporary storage position specified by 'temp_position'." + + if temp_position in self.temp_positions: + self.temp_positions.remove(temp_position) + + def set_frame_usage(self, node, extend): + + """ + Ensure that the frame usage for the unit associated with 'node' is set + on the 'extend' instruction. + """ + + ntemp = self.max_temp_position + 1 + extend.attr = ntemp + node.unit.local_usage # NOTE: See get_code for similar code. + + # Code writing methods. + + def new_op(self, op): + + "Add 'op' to the generated code." + + # Optimise load operations employed by this instruction. + + self.optimiser.optimise_load_operations(op) + if self.optimiser.optimise_away_no_operations(op): + return + + # Add the operation to the current block. + + self.blocks[-1].code.append(op) + self.optimiser.set_new(op) + + def remove_op(self): + + "Remove the last instruction." + + op = self.blocks[-1].code.pop() + self.optimiser.clear_active() + + def replace_op(self, op): + + "Replace the last added instruction with 'op'." + + self.remove_op() + self.new_op(op) + + def replace_active_value(self, op): + + """ + Replace the value-providing active instruction with 'op' if appropriate. + """ + + self.optimiser.remove_active_value() + self.new_op(op) + + def last_op(self): + + "Return the last added instruction." + + try: + return self.blocks[-1].code[-1] + except IndexError: + return None + + # Common methods. + + def _visitAttr(self, node, classes): + + """ + Visit the attribute-related 'node', generating instructions based on the + given 'classes'. + """ + + self.dispatch(node.expr) + self._generateAttr(node, node.attrname, classes) + + def _generateAttr(self, node, attrname, classes): + + """ + Generate code for the access to 'attrname' using the given 'classes'. + """ + + AddressInstruction, AddressContextInstruction, AddressContextCondInstruction, \ + AttrInstruction, AttrIndexInstruction, AttrIndexContextInstruction, \ + AttrIndexContextCondInstruction = classes + + # Where the last operation (defining the attribute owner) yields a + # constant... + + target_name = self.optimiser.optimise_constant_accessor() + + # Only try and discover the position if the target can be resolved. + # Since instances cannot be constants, this involves classes and + # modules. + + if target_name is not None: + + # Access the object table to get the attribute position. + + try: + table_entry = self.objtable.table[target_name] + except KeyError: + raise TranslateError(self.module.full_name(), node, + "No object entry exists for target %r." % target_name) + + try: + pos = table_entry[attrname] + except KeyError: + raise TranslateError(self.module.full_name(), node, + "No attribute entry exists for name %r in target %r." % (attrname, target_name)) + + # Produce a suitable instruction. + + if AddressInstruction is not None: + self.replace_active_value(AddressInstruction(pos)) + else: + raise TranslateError(self.module.full_name(), node, + "Storing of class or module attribute %r via an object is not permitted." % attrname) + + return + + # Where the last operation involves the special 'self' name, check to + # see if the attribute is acceptably positioned and produce a direct + # access to the attribute. + + # This is the only reliable way of detecting instance accesses at + # compile-time since in general, objects could be classes or modules, + # but 'self' should only refer to instances. + + elif self.optimiser.optimise_self_access(self.unit, attrname): + + # Either generate an instruction operating on an instance attribute. + + try: + attr = self.unit.parent.instance_attributes()[attrname] + self.new_op(AttrInstruction(attr)) + return + + # Or generate an instruction operating on a class attribute. + # NOTE: Any simple instruction providing self is not removed. + + except KeyError: + + try: + attr = self.unit.parent.all_attributes()[attrname] + + # Switch the context if the class attribute is compatible with + # the instance. + + if attr.defined_within_hierarchy(): + + # Only permit loading (not storing) of class attributes via self. + + if AddressContextInstruction is not None: + self.new_op(AddressContextInstruction(attr)) + else: + raise TranslateError(self.module.full_name(), node, + "Storing of class attribute %r via self not permitted." % attrname) + + # Preserve the context if the class attribute comes from an + # incompatible class. + + elif attr.defined_outside_hierarchy(): + + # Only permit loading (not storing) of class attributes via self. + + if AddressInstruction is not None: + self.new_op(AddressInstruction(attr)) + else: + raise TranslateError(self.module.full_name(), node, + "Storing of class attribute %r via self not permitted." % attrname) + + # Otherwise, test for a suitable context at run-time. + + else: + + # Only permit loading (not storing) of class attributes via self. + + if AddressContextCondInstruction is not None: + self.new_op(AddressContextCondInstruction(attr)) + else: + raise TranslateError(self.module.full_name(), node, + "Storing of class attribute %r via self not permitted." % attrname) + + return + + # Or delegate the attribute access to a general instruction + # since the kind of attribute cannot be deduced. + + except KeyError: + pass + + # Otherwise, perform a normal operation. + + try: + index = self.objtable.get_index(attrname) + + except self.objtable.TableError: + + # If this error arises on generated code, check the names_used + # attribute on the Importer. + + raise TranslateError(self.module.full_name(), node, + "No attribute entry exists for name %r." % attrname) + + # NOTE: Test for class vs. instance attributes, generating + # NOTE: context-related instructions. + + if AttrIndexContextCondInstruction is not None: + self.new_op(AttrIndexContextCondInstruction(index)) + + # Store instructions do not need to consider context modifications. + + else: + self.new_op(AttrIndexInstruction(index)) + + # Invocations involve the following: + # + # 1. Reservation of a frame for the arguments + # 2. Identification of the target which is then held in temporary storage + # 3. Optional inclusion of a context (important for methods) + # 4. Preparation of the argument frame + # 5. Invocation of the target + # 6. Discarding of the frame + # + # In order to support nested invocations - such as a(b(c)) - use of the + # temporary storage is essential. + + def _startCallFunc(self): + + "Record the location of the invocation." + + op = MakeFrame() + self.new_op(op) # records the start of the frame + self.frame_makers.append(op) + + def _generateCallFunc(self, args, node): + + """ + Support a generic function invocation using the given 'args', occurring + on the given 'node', where the expression providing the invocation + target has just been generated. + + In other situations, the invocation is much simpler and does not need to + handle the full flexibility of a typical Python invocation. Internal + invocations, such as those employed by operators and certain + control-flow mechanisms, use predetermined arguments and arguably do not + need to support the same things as the more general invocations. + """ + + target, context, temp = self._generateCallFuncContext() + self._generateCallFuncArgs(target, context, temp, args, node) + return temp, target + + def _generateCallFuncContext(self): + + """ + Produce code which loads and checks the context of the current + invocation, the instructions for whose target have already been + produced, returning a list of instructions which reference the + invocation target. + """ + + t = self.optimiser.optimise_known_target() + if t: + target, context = t + if isinstance(target, Instance): # lambda object + target, context = None, None + else: + target, context = None, None + + # Store the target in temporary storage for subsequent referencing. + # NOTE: This may not be appropriate for class invocations + # NOTE: (instantiation). + + temp = self.optimiser.optimise_temp_storage() + + # Where a target or context are not known or where an instance is known + # to be the context, load the context. + + if target is None or isinstance(context, Instance): + self.new_op(temp) + self.new_op(LoadContext()) + self.new_op(StoreFrame(0)) + + # For known instantiations, provide a new object as the first argument + # to the __init__ method. + + elif isinstance(target, Class): + self.make_object(target, len(target.instance_attributes())) + self.new_op(StoreFrame(0)) + + # Otherwise omit the context. + + else: + pass # NOTE: Class methods should be supported. + + return target, context, temp + + def _generateCallFuncArgs(self, target, context, temp, args, node): + + """ + Given invocation 'target' and 'context' information, the 'temp' + reference to the target, a list of nodes representing the 'args' + (arguments), generate instructions which load the arguments for the + invocation defined by the given 'node'. + """ + + # Evaluate the arguments. + + employed_positions = set() + employed_keywords = set() + extra_keywords = [] + + # Find keyword arguments in advance in order to help resolve targets. + + for arg in args: + if isinstance(arg, compiler.ast.Keyword): + employed_keywords.add(arg.name) + + possible_targets = self.paramtable.all_possible_objects(employed_keywords) + + # Note the presence of the context in the frame where appropriate. + + if target is None or isinstance(context, Instance): + ncontext = 1 + expect_context = 0 + + # Handle calls to classes. + + elif isinstance(target, Class): + ncontext = 1 + expect_context = 0 + target = target.get_init_method() + + # Method calls via classes. + + elif isinstance(context, Class): + ncontext = 0 + expect_context = 1 + + # Function calls. + + else: + ncontext = 0 + expect_context = 0 + + first = 1 + frame_pos = ncontext + max_keyword_pos = -1 + + for arg in args: + + # Handle positional and keyword arguments separately. + + if isinstance(arg, compiler.ast.Keyword): + + # Optimise where the target is known now. + + if target is not None: + + # Find the parameter table entry for the target. + + target_name = target.full_name() + + # Look for a callable with the precise target name. + + table_entry = self.paramtable.table[target_name] + + # Look the name up in the parameter table entry. + + try: + pos = table_entry[arg.name] + + # Where no position is found, this could be an extra keyword + # argument. + + except KeyError: + extra_keywords.append(arg) + continue + + # Test for illegal conditions. + + if pos in employed_positions: + raise TranslateError(self.module.full_name(), node, + "Keyword argument %r overwrites parameter %r." % (arg.name, pos)) + + employed_positions.add(pos) + + # Generate code for the keyword and the positioning + # operation. + + self.dispatch(arg.expr) + self.new_op(StoreFrame(pos)) + + # Otherwise, generate the code needed to obtain the details of + # the parameter location. + + else: + + # Combine the target details with the name to get the location. + # See the access method on the List class. + + try: + paramindex = self.paramtable.get_index(arg.name) + + # Where no position is found, this could be an extra keyword + # argument. + + except self.paramtable.TableError: + extra_keywords.append(arg) + continue + + # Generate code for the keyword and the positioning + # operation. Get the value as the source of the assignment. + + self.dispatch(arg.expr) + self.record_value() + + # Store the source value using the callable's parameter + # table information. + + self.new_op(temp) + self.new_op(StoreFrameIndex(paramindex)) + + self.set_source() + self.discard_value() + + # Record the highest possible frame position for this argument. + + max_keyword_pos = max(max_keyword_pos, max(self.paramtable.all_attribute_positions(arg.name))) + + else: + self.dispatch(arg) + self.new_op(StoreFrame(frame_pos)) + + employed_positions.add(frame_pos) + + # Check to see if the first argument is appropriate (compatible with + # the target where methods are being invoked via classes). + + if first and expect_context: + + # Drop any test if the target and the context are known. + + if not self.optimiser.have_correct_self_for_target(context, self.unit): + + continue_block = self.new_block() + + self.new_op(CheckSelf()) + self.optimiser.set_source(temp) + self.new_op(JumpIfTrue(continue_block)) + + # Where the context is inappropriate, drop the incomplete frame and + # raise an exception. + + self.new_op(DropFrame()) + self.new_op(LoadResult()) + + self.load_builtin("TypeError", node) + self.new_op(StoreException()) + self.new_op(RaiseException()) + + self.set_block(continue_block) + + first = 0 + frame_pos += 1 + + # NOTE: Extra keywords are not supported. + # NOTE: Somehow, the above needs to be combined with * arguments. + + if extra_keywords: + print "Warning: extra keyword argument(s) %s not handled." % ", ".join([arg.name for arg in extra_keywords]) + + # Either test for a complete set of arguments. + + if target is not None: + + # Make sure that enough arguments have been given. + + nargs_max = len(target.positional_names) + ndefaults = len(target.defaults) + nargs_min = nargs_max - ndefaults + + for i in range(ncontext, nargs_min): + if i not in employed_positions: + raise TranslateError(self.module.full_name(), node, + "Argument %r not supplied for %r: need at least %d argument(s)." % (i+1, target.name, nargs_min)) + + nargs = frame_pos + + if nargs > nargs_max and not target.has_star and not target.has_dstar: + raise TranslateError(self.module.full_name(), node, + "Too many arguments for %r: need at most %d argument(s)." % (target.name, nargs_max)) + + # Where defaults are involved, put them into the frame. + + self._generateCallFuncDefaultArgs(target, temp, nargs_min, nargs_max, employed_positions) + + # Set the frame size. + + self._endCallFuncArgs(nargs_max) + + # Or generate instructions to do this at run-time. + + else: + max_pos = max(max(employed_positions or [-1]), max_keyword_pos, frame_pos - 1) + + # Only check non-empty frames (using the callable's details). + + if employed_positions or max_pos >= 0: + self.new_op(temp) + self.new_op(CheckFrame(max_pos + 1)) + + # Set the frame size. + + self._endCallFuncArgs(max_pos + 1) + + def _generateCallFuncDefaultArgs(self, target, temp, nargs_min, nargs_max, employed_positions): + + """ + For the given 'target' and 'temp' reference to the target, generate + default arguments for those positions in the range 'nargs_min'... + 'nargs_max' which are not present in the 'employed_positions' + collection. + """ + + # Where a lambda is involved, construct a dynamic object to hold the + # defaults. + + dynamic = target.name is None + + # Here, we use negative index values to visit the right hand end of + # the defaults list. + + for pos in range(nargs_min, nargs_max): + if pos not in employed_positions: + if dynamic: + self.new_op(temp) + self.new_op(LoadAttr(target.default_attrs[pos - nargs_min])) + else: + self.new_op(LoadAddress(target.default_attrs[pos - nargs_min])) + self.new_op(StoreFrame(pos)) + + def _doCallFunc(self, instruction, target=None): + + "Make the invocation." + + if isinstance(target, Class): + self.new_op(LoadConst(target.get_init_method())) + else: + self.new_op(instruction) + self.new_op(LoadCallable()) + self.new_op(JumpWithFrame()) + + def _endCallFuncArgs(self, nargs): + + "Set the frame size." + + self.frame_makers[-1].attr = nargs + self.frame_makers.pop() + + def _endCallFunc(self, instruction=None, target=None, load_result=1): + + "Finish the invocation and tidy up afterwards." + + if isinstance(target, Class): + self.new_op(LoadName(target.get_init_method().all_locals()["self"])) # load the context in the invocation frame + self.new_op(StoreResult()) + self.new_op(DropFrame()) + if load_result: + self.new_op(LoadResult()) + + # Discard any temporary storage instructions. + + if instruction is not None: + self.discard_temp(instruction) + + def _generateFunctionDefaults(self, function): + + """ + Generate the default initialisation code for 'function', returning + a temporary storage reference if a dynamic object was created for the + function. + """ + + attr_to_default = zip(function.default_attrs, function.defaults) + if not attr_to_default: + return None + + # Where a lambda is involved, construct a dynamic object to hold the + # defaults. + + dynamic = function.name is None + + if dynamic: + self.make_object(self.get_builtin_class("function", function), len(attr_to_default)) + temp = self.get_temp() + + for attr, default in attr_to_default: + self.dispatch(default) + + self.record_value() + if dynamic: + self.new_op(temp) + self.new_op(StoreAttr(attr)) + else: + self.new_op(StoreAddress(attr)) + self.set_source() + self.discard_value() + + if dynamic: + return temp + else: + return None + + 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, AddressInstruction = classes + + if scope == "local": + unit = self.unit + if isinstance(unit, Function): + self.new_op(NameInstruction(unit.all_locals()[name])) + elif isinstance(unit, Class): + self.new_op(AddressInstruction(unit.all_class_attributes()[name])) + elif isinstance(unit, Module): + self.new_op(AddressInstruction(unit.module_attributes()[name])) + else: + raise TranslateError(self.module.full_name(), node, "Program unit %r has no local %r." % (unit, name)) + + elif scope == "global": + globals = self.module.module_attributes() + if globals.has_key(name): + self.new_op(AddressInstruction(globals[name])) + else: + raise TranslateError(self.module.full_name(), node, "Module %r has no attribute %r." % (self.module, name)) + + else: + self.new_op(AddressInstruction(self.get_builtin(name, node))) + + def _visitUnary(self, node): + + """ + _t = node.expr + try: + _result = _t.__pos__() + except AttributeError: + raise TypeError + """ + + method = unary_methods[node.__class__.__name__] + + type_error_block = self.new_block() + end_block = self.new_block() + + # Evaluate and store the operand in temporary storage. + + self.dispatch(node.expr) + temp = self.optimiser.optimise_temp_storage() + + self.new_op(temp) + + # Get the method on temp. + + self._generateAttr(node, method, self.attribute_load_instructions) + temp_method = self.optimiser.optimise_temp_storage() + + self._handleAttributeError(node, type_error_block) + + # Add arguments. + # NOTE: No support for defaults. + + self._startCallFunc() + self.new_op(temp) # Explicit context as first argument. + self.new_op(StoreFrame(0)) + self._endCallFuncArgs(1) + self._doCallFunc(temp_method) + self._endCallFunc(temp_method) + self.new_op(Jump(end_block)) + + # Store the result. + + temp_out = self.get_temp() + + # Raise a TypeError. + + self.set_block(type_error_block) + self.load_builtin("TypeError", node) + self.new_op(StoreException()) + self.new_op(RaiseException()) + + self.set_block(end_block) + + # Produce the result. + + self.new_op(temp_out) + + # Compilation duties... + + self.discard_temp(temp) + self.discard_temp(temp_out) + + def _visitBinary(self, node): + + """ + _t1 = node.left + _t2 = node.right + try: + _result = _t1.__add__(_t2) + if _result is NotImplemented: + raise AttributeError + except AttributeError: + try: + _result = _t2.__radd__(_t1) + if _result is NotImplemented: + raise AttributeError + except AttributeError: + raise TypeError + """ + + left_method, right_method = binary_methods[node.__class__.__name__] + + # Evaluate and store the left operand in temporary storage. + + self.dispatch(node.left) + temp1 = self.optimiser.optimise_temp_storage() + + # Evaluate and store the right operand in temporary storage. + + self.dispatch(node.right) + temp2 = self.optimiser.optimise_temp_storage() + + temp_out = self._generateBinary(node, temp1, temp2, left_method, right_method) + + # Produce the result. + + self.new_op(temp_out) + + # Compilation duties... + + self.discard_temp(temp1) + self.discard_temp(temp2) + self.discard_temp(temp_out) + + def _generateBinary(self, node, temp1, temp2, left_method, right_method): + + """ + For the given 'node', generate the binary operator pattern for the + operands 'temp1' and 'temp2', employing 'left_method' and 'right_method' + as defined for binary operators, but also used in comparisons (for which + this method is provided). + + A temporary storage reference is returned from this method. + """ + + right_block = self.new_block() + type_error_block = self.new_block() + end_block = self.new_block() + + # Left method. + + temp_out = self._generateOpMethod(node, temp1, temp2, left_method, right_block, end_block) + self.discard_temp(temp_out) # NOTE: Will re-use the same storage. + + # Right method. + + self.set_block(right_block) + temp_out = self._generateOpMethod(node, temp2, temp1, right_method, type_error_block, end_block) + + # Raise a TypeError. + + self.set_block(type_error_block) + self.load_builtin("TypeError", node) + self.new_op(StoreException()) + self.new_op(RaiseException()) + + self.set_block(end_block) + return temp_out + + def _generateOpMethod(self, node, temp1, temp2, method_name, next_method_block, end_block): + + """ + For the given 'node', generate the operator method invocation using the + operands 'temp1' and 'temp2', employing the given 'method_name', and + jumping appropriately to 'next_method_block' where a NotImplemented + result is returned, or to 'end_block' if the method call was successful. + + A temporary storage reference is returned from this method. + """ + + end_attempt_block = self.new_block() + + self.new_op(temp1) + + # Get method on temp1. + + self._generateAttr(node, method_name, self.attribute_load_instructions) + temp_method = self.optimiser.optimise_temp_storage() + + self._handleAttributeError(node, end_attempt_block) + + # Add arguments. + # NOTE: No support for defaults. + + self._startCallFunc() + self.new_op(temp1) + self.new_op(StoreFrame(0)) + self.new_op(temp2) + self.new_op(StoreFrame(1)) + self._endCallFuncArgs(2) + self._doCallFunc(temp_method) + self._endCallFunc(temp_method) + + # Store the result. + + temp_out = self.get_temp() + + # Test for NotImplemented. + # Don't actually raise an exception. + + self.new_op(TestIdentityAddress(self.importer.get_predefined_constant("NotImplemented"))) + self.new_op(JumpIfTrue(next_method_block)) + self.new_op(Jump(end_block)) + + # End method attempt. + + self.set_block(end_attempt_block) + return temp_out + + def _handleAttributeError(self, node, end_call_block): + + """ + Add exception handling to the method acquisition instructions where the + attribute access cannot be resolved at compile-time. + """ + + if not self.optimiser.optimise_known_target(): + self.load_builtin("AttributeError", node) + self.new_op(CheckException()) + self.new_op(JumpIfTrue(end_call_block)) + + def _generateSequence(self, sequence_type, node): + + "Make a sequence of 'sequence_type' for the given program 'node'." + + self.make_object(self.get_builtin_class(sequence_type, node), len(node.nodes)) + temp = self.get_temp() + + for i, n in enumerate(node.nodes): + self.dispatch(n) + self.record_value() + self.new_op(temp) + self.new_op(StoreAttr(Attr(i, None, None))) + self.set_source() + self.discard_value() + + self.new_op(temp) + self.discard_temp(temp) + + def _generateTestBoolean(self, node, temp): + + """ + Generate a test of the boolean status of the current value for the given + program 'node'. + """ + + # Get method on temp. + # NOTE: Using __bool__ instead of __nonzero__. + + self._generateAttr(node, "__bool__", self.attribute_load_instructions) + temp_method = self.optimiser.optimise_temp_storage() + + self._startCallFunc() + self.new_op(temp) + self.new_op(StoreFrame(0)) + self._endCallFuncArgs(1) + self._doCallFunc(temp_method) + self._endCallFunc(temp_method) + + self.discard_temp(temp_method) + + # Convert result to boolean (a StoreBoolean operation). + + self.new_op(TestIdentityAddress(self.importer.get_predefined_constant("True"))) + + def _generateLoadBoolean(self, node): + + """ + Generate instructions to load the appropriate value given the current + boolean status. + """ + + true_block = self.new_block() + end_block = self.new_block() + + self.new_op(JumpIfTrue(true_block)) + self.new_op(LoadAddress(self.importer.get_predefined_constant("False"))) + self.new_op(Jump(end_block)) + + self.set_block(true_block) + self.new_op(LoadAddress(self.importer.get_predefined_constant("True"))) + + self.set_block(end_block) + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r 59637ebeb668 -r 072c14d15074 rsvp.py --- a/rsvp.py Fri Mar 20 00:32:43 2009 +0100 +++ b/rsvp.py Sun Apr 05 03:05:59 2009 +0200 @@ -78,16 +78,21 @@ "A really simple virtual processor." - def __init__(self, memory, objlist, paramlist, pc=None, debug=0): + def __init__(self, memory, objlist, paramlist, true_constant, false_constant, pc=None, debug=0): """ Initialise the processor with a 'memory' (a list of values containing - instructions and data) and the optional program counter 'pc'. + instructions and data), the object and parameter lists 'objlist' and + 'paramlist', the addresses 'true_constant' and 'false_constant', and the + optional program counter 'pc'. """ self.memory = memory self.objlist = objlist.as_raw() self.paramlist = paramlist.as_raw() + self.true_constant = true_constant + self.false_constant = false_constant + self.pc = pc or 0 self.debug = debug @@ -120,6 +125,7 @@ # Native class constants. cls = objlist.access("__builtins__", "int") + self.int_class_location = cls and cls.get_value() and cls.get_value().location self.int_instance_location = cls and cls.get_value() and cls.get_value().instance_template_location # Debugging attributes. @@ -307,8 +313,11 @@ # of proper locations. if isinstance(addr, str): - self.native_functions[addr](self) - return next + handler = self.native_functions[addr](self) + if handler is None: + return next + else: + return handler else: self.push_pc(self.pc + 1) return addr @@ -639,7 +648,7 @@ # Test operand suitability. - if not self._CheckInstance(left, self.int_instance_location) and self._CheckInstance(right, self.int_instance_location): + if not self._CheckInstance(left, self.int_class_location) and self._CheckInstance(right, self.int_class_location): self.exception = self.type_error return self.RaiseException() @@ -651,6 +660,10 @@ # Make a new object. addr = self._MakeObject(2, self.int_instance_location) + + # Store the result. + # NOTE: The data is considered ready to use. + self.save(addr + 1, self.load(left_data) + self.load(right_data)) # Return the new object. @@ -658,10 +671,36 @@ self.result = addr, addr + def builtins_int_bool(self): + frame = self.local_sp_stack[-1] + + # Get operands addresses. + + left_context, left = self.frame_stack[frame] + + # Test operand suitability. + + if not self._CheckInstance(left, self.int_class_location): + self.exception = self.type_error + return self.RaiseException() + + # NOTE: Assume single location for data. + + left_data = left + 1 + + # Test the data. + # NOTE: The data is considered ready to use. + + if self.load(left_data) != 0: + self.result = self.true_constant, self.true_constant + else: + self.result = self.false_constant, self.false_constant + native_functions = { "__builtins__.object.__init__" : builtins_object_init, "__builtins__.int.__init__" : builtins_int_init, "__builtins__.int.__add__" : builtins_int_add, + "__builtins__.int.__bool__" : builtins_int_bool, } # vim: tabstop=4 expandtab shiftwidth=4 diff -r 59637ebeb668 -r 072c14d15074 test.py --- a/test.py Fri Mar 20 00:32:43 2009 +0100 +++ b/test.py Sun Apr 05 03:05:59 2009 +0200 @@ -40,7 +40,10 @@ print "Getting raw image..." rc = program.get_raw_image() print "Initialising the machine..." - rm = rsvp.RSVPMachine(rc, objlist, paramlist, debug=debug) + importer = program.get_importer() + true_constant = importer.get_constant(True).location + false_constant = importer.get_constant(False).location + rm = rsvp.RSVPMachine(rc, objlist, paramlist, true_constant, false_constant, debug=debug) rm.pc = program.code_location return rm diff -r 59637ebeb668 -r 072c14d15074 tests/swap.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/swap.py Sun Apr 05 03:05:59 2009 +0200 @@ -0,0 +1,7 @@ +#!/usr/bin/env python + +a = 1 +b = 2 +b, a = a, b + +# vim: tabstop=4 expandtab shiftwidth=4