# HG changeset patch # User Paul Boddie # Date 1476913241 -7200 # Node ID 4ad5dbbc7cc8a499775a859c9592062e74fd191d # Parent a49d9557271790387f2e1c03e592f6fdff4606fc Re-introduced support for the translation of programs. diff -r a49d95572717 -r 4ad5dbbc7cc8 encoders.py --- a/encoders.py Wed Oct 19 22:16:27 2016 +0200 +++ b/encoders.py Wed Oct 19 23:40:41 2016 +0200 @@ -153,11 +153,11 @@ if args: a = [] - for i in args: - if isinstance(i, tuple): - a.append(encode_instruction(i)) + for arg in args: + if isinstance(arg, tuple): + a.append(encode_instruction(arg)) else: - a.append(i or "{}") + a.append(arg or "{}") argstr = "(%s)" % ", ".join(a) return "%s%s" % (op, argstr) else: @@ -167,6 +167,90 @@ # Output program encoding. +attribute_ops = ( + "__load_via_class", "__load_via_object", + "__store_via_object", + ) + +checked_ops = ( + "__check_and_load_via_class", "__check_and_load_via_object", "__check_and_load_via_any", + "__check_and_store_via_class", "__check_and_store_via_object", "__check_and_store_via_any", + ) + +typename_ops = ( + "__test_common_instance", + ) + +def encode_access_instruction(instruction, subs): + + """ + Encode the 'instruction' - a sequence starting with an operation and + followed by arguments, each of which may be an instruction sequence or a + plain value - to produce a function call string representation. + + The 'subs' parameter defines a mapping of substitutions for special values + used in instructions. + """ + + op = instruction[0] + args = instruction[1:] + + if not args: + argstr = "" + + else: + # Encode the arguments. + + a = [] + for arg in args: + a.append(encode_access_instruction_arg(arg, subs)) + + # Modify certain arguments. + + # Convert attribute name arguments to position symbols. + + if op in attribute_ops: + arg = a[1] + a[1] = encode_symbol("pos", arg) + + # Convert attribute name arguments to position and code symbols. + + elif op in checked_ops: + arg = a[1] + a[1] = encode_symbol("pos", arg) + a.insert(2, encode_symbol("code", arg)) + + # Convert type name arguments to position and code symbols. + + elif op in typename_ops: + arg = "#" % a[1] + a[1] = encode_symbol("pos", arg) + a.insert(2, encode_symbol("code", arg)) + + argstr = "(%s)" % ", ".join(a) + + # Substitute the first element of the instruction, which may not be an + # operation at all. + + return "%s%s" % (subs.get(op, op), argstr) + +def encode_access_instruction_arg(arg, subs): + + "Encode 'arg' using 'subs' to define substitutions." + + if isinstance(arg, tuple): + return encode_access_instruction(arg, subs) + + # Special values only need replacing, not encoding. + + elif subs.has_key(arg): + return subs.get(arg) + + # Other values may need encoding. + + else: + return encode_path(arg) + def encode_function_pointer(path): "Encode 'path' as a reference to an output program function." diff -r a49d95572717 -r 4ad5dbbc7cc8 lplc --- a/lplc Wed Oct 19 22:16:27 2016 +0200 +++ b/lplc Wed Oct 19 23:40:41 2016 +0200 @@ -3,7 +3,7 @@ from errors import * from os.path import abspath, exists, join, split from time import time -import importer, deducer, optimiser +import importer, deducer, optimiser, translator import sys libdirs = [ @@ -70,6 +70,11 @@ now = stopwatch("Optimisation", now) + t = translator.Translator(i, d, o, "_generated") + t.to_output() + + stopwatch("Translation", now) + # Report any errors. except ProcessingError, exc: diff -r a49d95572717 -r 4ad5dbbc7cc8 optimiser.py --- a/optimiser.py Wed Oct 19 22:16:27 2016 +0200 +++ b/optimiser.py Wed Oct 19 23:40:41 2016 +0200 @@ -381,8 +381,8 @@ # Prevent re-evaluation of any dynamic expression by storing it. if original_accessor == "": - emit(("set_accessor", original_accessor)) - accessor = context_var = ("accessor",) + emit(("__set_accessor", original_accessor)) + accessor = context_var = ("",) else: accessor = context_var = (original_accessor,) @@ -393,25 +393,25 @@ # Prevent re-evaluation of any dynamic expression by storing it. if original_accessor == "": - emit(("set_accessor", original_accessor)) - accessor = ("accessor",) + emit(("__set_accessor", original_accessor)) + accessor = ("",) else: accessor = (original_accessor,) # Apply any test. if test == "specific-type": - accessor = ("test_specific_type", accessor, test_type) + accessor = ("__test_specific_type", accessor, test_type) elif test == "specific-instance": - accessor = ("test_specific_instance", accessor, test_type) + accessor = ("__test_specific_instance", accessor, test_type) elif test == "specific-object": - accessor = ("test_specific_object", accessor, test_type) + accessor = ("__test_specific_object", accessor, test_type) elif test == "common-type": - accessor = ("test_common_type", accessor, test_type) + accessor = ("__test_common_type", accessor, test_type) elif test == "common-instance": - accessor = ("test_common_instance", accessor, test_type) + accessor = ("__test_common_instance", accessor, test_type) elif test == "common-object": - accessor = ("test_common_object", accessor, test_type) + accessor = ("__test_common_object", accessor, test_type) # Perform the first or final access. # The access only needs performing if the resulting accessor is used. @@ -422,39 +422,39 @@ if first_method == "relative-class": if assigning: - emit(("store_via_class", accessor, attrname, "")) + emit(("__store_via_class", accessor, attrname, "")) else: - accessor = ("load_via_class", accessor, attrname) + accessor = ("__load_via_class", accessor, attrname) elif first_method == "relative-object": if assigning: - emit(("store_via_object", accessor, attrname, "")) + emit(("__store_via_object", accessor, attrname, "")) else: - accessor = ("load_via_object", accessor, attrname) + accessor = ("__load_via_object", accessor, attrname) elif first_method == "relative-object-class": if assigning: - emit(("get_class_and_store", accessor, attrname, "")) + emit(("__get_class_and_store", accessor, attrname, "")) else: - accessor = ("get_class_and_load", accessor, attrname) + accessor = ("__get_class_and_load", accessor, attrname) elif first_method == "check-class": if assigning: - emit(("check_and_store_via_class", accessor, attrname, "")) + emit(("__check_and_store_via_class", accessor, attrname, "")) else: - accessor = ("check_and_load_via_class", accessor, attrname) + accessor = ("__check_and_load_via_class", accessor, attrname) elif first_method == "check-object": if assigning: - emit(("check_and_store_via_object", accessor, attrname, "")) + emit(("__check_and_store_via_object", accessor, attrname, "")) else: - accessor = ("check_and_load_via_object", accessor, attrname) + accessor = ("__check_and_load_via_object", accessor, attrname) elif first_method == "check-object-class": if assigning: - emit(("check_and_store_via_any", accessor, attrname, "")) + emit(("__check_and_store_via_any", accessor, attrname, "")) else: - accessor = ("check_and_load_via_any", accessor, attrname) + accessor = ("__check_and_load_via_any", accessor, attrname) # Traverse attributes using the accessor. @@ -465,8 +465,8 @@ # Set the context, if appropriate. if remaining == 1 and final_method != "assign" and context == "final-accessor": - emit(("set_context", accessor)) - accessor = context_var = "context" + emit(("__set_context", accessor)) + accessor = context_var = "" # Perform the access only if not achieved directly. @@ -474,14 +474,14 @@ if traversal_mode == "class": if assigning: - emit(("store_via_class", accessor, attrname, "")) + emit(("__store_via_class", accessor, attrname, "")) else: - accessor = ("load_via_class", accessor, attrname) + accessor = ("__load_via_class", accessor, attrname) else: if assigning: - emit(("store_via_object", accessor, attrname, "")) + emit(("__store_via_object", accessor, attrname, "")) else: - accessor = ("load_via_object", accessor, attrname) + accessor = ("__load_via_object", accessor, attrname) remaining -= 1 @@ -492,29 +492,29 @@ # Set the context, if appropriate. if remaining == 1 and final_method != "assign" and context == "final-accessor": - emit(("set_context", accessor)) - accessor = context_var = "context" + emit(("__set_context", accessor)) + accessor = context_var = "" # Perform the access only if not achieved directly. if remaining > 1 or final_method in ("access", "assign"): if assigning: - emit(("check_and_store_via_any", accessor, attrname, "")) + emit(("__check_and_store_via_any", accessor, attrname, "")) else: - accessor = ("check_and_load_via_any", accessor, attrname) + accessor = ("__check_and_load_via_any", accessor, attrname) remaining -= 1 if final_method == "static-assign": - emit(("store_member", origin, "")) + emit(("__store_member", origin, "")) elif final_method == "static": - accessor = ("load_static", origin) + accessor = ("__load_static", origin) if context_test == "test": - emit(("test_context", context_var, accessor)) + emit(("__test_context", context_var, accessor)) elif context_test == "replace": - emit(("replace_context", context_var, accessor)) + emit(("__replace_context", context_var, accessor)) elif final_method not in ("assign", "static-assign"): emit(accessor) diff -r a49d95572717 -r 4ad5dbbc7cc8 translator.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/translator.py Wed Oct 19 23:40:41 2016 +0200 @@ -0,0 +1,1105 @@ +#!/usr/bin/env python + +""" +Translate programs. + +Copyright (C) 2015, 2016 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 common import * +from encoders import * +from os.path import exists, join +from os import makedirs +import compiler +import results + +class Translator(CommonOutput): + + "A program translator." + + def __init__(self, importer, deducer, optimiser, output): + self.importer = importer + self.deducer = deducer + self.optimiser = optimiser + self.output = output + + def to_output(self): + output = join(self.output, "src") + + if not exists(output): + makedirs(output) + + self.check_output() + + for module in self.importer.modules.values(): + tm = TranslatedModule(module.name, self.importer, self.deducer, self.optimiser) + tm.translate(module.filename, join(output, "%s.c" % module.name)) + +# Classes representing intermediate translation results. + +class TranslationResult: + + "An abstract translation result mix-in." + + pass + +class Expression(results.Result, TranslationResult): + + "A general expression." + + def __init__(self, s): + self.s = s + def __str__(self): + return self.s + def __repr__(self): + return "Expression(%r)" % self.s + +class TrResolvedNameRef(results.ResolvedNameRef, TranslationResult): + + "A reference to a name in the translation." + + def __str__(self): + + "Return an output representation of the referenced name." + + # Use any alias in preference to the origin of the name. + + name = self.get_name() + + # Use any static origin in preference to any alias. + # For sources, any identified static origin will be constant and thus + # usable directly. For targets, no constant should be assigned and thus + # the alias (or any plain name) will be used. + + origin = self.static() and self.get_origin() + name = origin and encode_path(origin) or name and encode_path(name) or encode_path(self.name) + + # Assignments. + + if self.expr: + + # Eliminate assignments between constants. + + if self.static() and isinstance(self.expr, results.ResolvedNameRef) and self.expr.static(): + return "" + else: + return "%s = %s" % (name, self.expr) + + # Expressions. + + else: + return name + +class TrConstantValueRef(results.ConstantValueRef, TranslationResult): + + "A constant value reference in the translation." + + def __str__(self): + return "const%d" % self.number + +class TrLiteralSequenceRef(results.LiteralSequenceRef, TranslationResult): + + "A reference representing a sequence of values." + + def __str__(self): + return str(self.node) + +class AttrResult(Expression, TranslationResult): + + "A translation result for an attribute access." + + def __init__(self, s, refs): + Expression.__init__(self, s) + self.refs = refs + + def get_origin(self): + return self.refs and len(self.refs) == 1 and first(self.refs).get_origin() + + def __repr__(self): + return "AttrResult(%r, %r)" % (self.s, self.origin) + +class PredefinedConstantRef(AttrResult): + + "A predefined constant reference." + + def __init__(self, value): + self.value = value + self.only_function_attrs = False + + def __str__(self): + return self.value + + def __repr__(self): + return "PredefinedConstantRef(%r)" % self.value + +def make_expression(expr): + + "Make a new expression from the existing 'expr'." + + if isinstance(expr, results.Result): + return expr + else: + return Expression(str(expr)) + +# The actual translation process itself. + +class TranslatedModule(CommonModule): + + "A module translator." + + def __init__(self, name, importer, deducer, optimiser): + CommonModule.__init__(self, name, importer) + self.deducer = deducer + self.optimiser = optimiser + + # Output stream. + + self.out = None + self.indent = 0 + self.tabstop = " " + + # Recorded namespaces. + + self.namespaces = [] + self.in_conditional = False + + # Attribute access counting. + + self.attr_accesses = {} + + def __repr__(self): + return "TranslatedModule(%r, %r)" % (self.name, self.importer) + + def translate(self, filename, output_filename): + + """ + Parse the file having the given 'filename', writing the translation to + the given 'output_filename'. + """ + + self.parse_file(filename) + + # Collect function namespaces for separate processing. + + self.record_namespaces(self.astnode) + + # Reset the lambda naming (in order to obtain the same names again) and + # translate the program. + + self.reset_lambdas() + + self.out = open(output_filename, "w") + try: + # Process namespaces, writing the translation. + + for path, node in self.namespaces: + self.process_namespace(path, node) + + # Process the module namespace including class namespaces. + + self.process_namespace([], self.astnode) + + finally: + self.out.close() + + def have_object(self): + + "Return whether a namespace is a recorded object." + + return self.importer.objects.get(self.get_namespace_path()) + + def get_builtin(self, name): + return self.importer.get_object("__builtins__.%s" % name) + + def in_method(self, path): + class_name, method_name = path.rsplit(".", 1) + return self.importer.classes.has_key(class_name) and class_name + + # Namespace recording. + + def record_namespaces(self, node): + + "Process the program structure 'node', recording namespaces." + + for n in node.getChildNodes(): + self.record_namespaces_in_node(n) + + def record_namespaces_in_node(self, node): + + "Process the program structure 'node', recording namespaces." + + # Function namespaces within modules, classes and other functions. + # Functions appearing within conditional statements are given arbitrary + # names. + + if isinstance(node, compiler.ast.Function): + self.record_function_node(node, (self.in_conditional or self.in_function) and self.get_lambda_name() or node.name) + + elif isinstance(node, compiler.ast.Lambda): + self.record_function_node(node, self.get_lambda_name()) + + # Classes are visited, but may be ignored if inside functions. + + elif isinstance(node, compiler.ast.Class): + self.enter_namespace(node.name) + if self.have_object(): + self.record_namespaces(node) + self.exit_namespace() + + # Conditional nodes are tracked so that function definitions may be + # handled. Since "for" loops are converted to "while" loops, they are + # included here. + + elif isinstance(node, (compiler.ast.For, compiler.ast.If, compiler.ast.While)): + in_conditional = self.in_conditional + self.in_conditional = True + self.record_namespaces(node) + self.in_conditional = in_conditional + + # All other nodes are processed depth-first. + + else: + self.record_namespaces(node) + + def record_function_node(self, n, name): + + """ + Record the given function, lambda, if expression or list comprehension + node 'n' with the given 'name'. + """ + + self.in_function = True + self.enter_namespace(name) + + if self.have_object(): + + # Record the namespace path and the node itself. + + self.namespaces.append((self.namespace_path[:], n)) + self.record_namespaces_in_node(n.code) + + self.exit_namespace() + self.in_function = False + + # Constant referencing. + + def get_literal_instance(self, n, name): + + """ + For node 'n', return a reference for the type of the given 'name'. + """ + + ref = self.get_builtin(name) + + if name in ("dict", "list", "tuple"): + return self.process_literal_sequence_node(n, name, ref, TrLiteralSequenceRef) + else: + path = self.get_namespace_path() + local_number = self.importer.all_constants[path][n.value] + constant_name = "$c%d" % local_number + objpath = self.get_object_path(constant_name) + number = self.optimiser.constant_numbers[objpath] + return TrConstantValueRef(constant_name, ref.instance_of(), n.value, number) + + # Namespace translation. + + def process_namespace(self, path, node): + + """ + Process the namespace for the given 'path' defined by the given 'node'. + """ + + self.namespace_path = path + + if isinstance(node, (compiler.ast.Function, compiler.ast.Lambda)): + self.in_function = True + self.process_function_body_node(node) + else: + self.in_function = False + self.invocation_depth = 0 + self.invocation_argument_depth = 0 + self.invocation_kw_argument_depth = 0 + self.start_module() + self.process_structure(node) + self.end_module() + + def process_structure(self, node): + + "Process the given 'node' or result." + + if isinstance(node, results.Result): + return node + else: + return CommonModule.process_structure(self, node) + + def process_structure_node(self, n): + + "Process the individual node 'n'." + + # Plain statements emit their expressions. + + if isinstance(n, compiler.ast.Discard): + expr = self.process_structure_node(n.expr) + self.statement(expr) + + # Nodes using operator module functions. + + elif isinstance(n, compiler.ast.Operator): + return self.process_operator_node(n) + + elif isinstance(n, compiler.ast.AugAssign): + self.process_augassign_node(n) + + elif isinstance(n, compiler.ast.Compare): + return self.process_compare_node(n) + + elif isinstance(n, compiler.ast.Slice): + return self.process_slice_node(n) + + elif isinstance(n, compiler.ast.Sliceobj): + return self.process_sliceobj_node(n) + + elif isinstance(n, compiler.ast.Subscript): + return self.process_subscript_node(n) + + # Classes are visited, but may be ignored if inside functions. + + elif isinstance(n, compiler.ast.Class): + self.process_class_node(n) + + # Functions within namespaces have any dynamic defaults initialised. + + elif isinstance(n, compiler.ast.Function): + self.process_function_node(n) + + # Lambdas are replaced with references to separately-generated + # functions. + + elif isinstance(n, compiler.ast.Lambda): + return self.process_lambda_node(n) + + # Assignments. + + elif isinstance(n, compiler.ast.Assign): + + # Handle each assignment node. + + for node in n.nodes: + self.process_assignment_node(node, n.expr) + + # Assignments within non-Assign nodes. + # NOTE: Cover all possible nodes employing these. + + elif isinstance(n, compiler.ast.AssName): + self.process_assignment_node(n, compiler.ast.Name("$temp")) + + elif isinstance(n, compiler.ast.AssAttr): + self.process_attribute_access(n) + + # Accesses. + + elif isinstance(n, compiler.ast.Getattr): + return self.process_attribute_access(n) + + # Names. + + elif isinstance(n, compiler.ast.Name): + return self.process_name_node(n) + + # Loops and conditionals. + + elif isinstance(n, compiler.ast.For): + self.process_for_node(n) + + elif isinstance(n, compiler.ast.While): + self.process_while_node(n) + + elif isinstance(n, compiler.ast.If): + self.process_if_node(n) + + elif isinstance(n, (compiler.ast.And, compiler.ast.Or)): + return self.process_logical_node(n) + + elif isinstance(n, compiler.ast.Not): + return self.process_not_node(n) + + # Exception control-flow tracking. + + elif isinstance(n, compiler.ast.TryExcept): + self.process_try_node(n) + + elif isinstance(n, compiler.ast.TryFinally): + self.process_try_finally_node(n) + + # Control-flow modification statements. + + elif isinstance(n, compiler.ast.Break): + self.writeline("break;") + + elif isinstance(n, compiler.ast.Continue): + self.writeline("continue;") + + elif isinstance(n, compiler.ast.Return): + expr = self.process_structure_node(n.value) + if expr: + self.writeline("return %s;" % expr) + else: + self.writeline("return;") + + # Invocations. + + elif isinstance(n, compiler.ast.CallFunc): + return self.process_invocation_node(n) + + elif isinstance(n, compiler.ast.Keyword): + return self.process_structure_node(n.expr) + + # Constant usage. + + elif isinstance(n, compiler.ast.Const): + return self.get_literal_instance(n, n.value.__class__.__name__) + + elif isinstance(n, compiler.ast.Dict): + return self.get_literal_instance(n, "dict") + + elif isinstance(n, compiler.ast.List): + return self.get_literal_instance(n, "list") + + elif isinstance(n, compiler.ast.Tuple): + return self.get_literal_instance(n, "tuple") + + # All other nodes are processed depth-first. + + else: + self.process_structure(n) + + def process_assignment_node(self, n, expr): + + "Process the individual node 'n' to be assigned the contents of 'expr'." + + # Names and attributes are assigned the entire expression. + + if isinstance(n, compiler.ast.AssName): + name_ref = self.process_name_node(n, self.process_structure_node(expr)) + self.statement(name_ref) + + elif isinstance(n, compiler.ast.AssAttr): + self.statement(self.process_attribute_access(n, self.process_structure_node(expr))) + + # Lists and tuples are matched against the expression and their + # items assigned to expression items. + + elif isinstance(n, (compiler.ast.AssList, compiler.ast.AssTuple)): + self.process_assignment_node_items(n, expr) + + # Slices and subscripts are permitted within assignment nodes. + + elif isinstance(n, compiler.ast.Slice): + self.statement(self.process_slice_node(n, expr)) + + elif isinstance(n, compiler.ast.Subscript): + self.statement(self.process_subscript_node(n, expr)) + + def process_attribute_access(self, n, expr=None): + + """ + Process the given attribute access node 'n'. + + Where a name is provided, a single access should be recorded + involving potentially many attributes, thus providing a path to an + object. The remaining attributes are then accessed dynamically. + The remaining accesses could be deduced and computed, but they would + also need to be tested. + + Where no name is provided, potentially many accesses should be + recorded, one per attribute name. These could be used to provide + computed accesses, but the accessors would need to be tested in each + case. + """ + + # Obtain any completed chain and return the reference to it. + + attr_expr = self.process_attribute_chain(n) + if self.have_access_expression(n): + return attr_expr + + # Where the start of the chain of attributes has been reached, process + # the complete access. + + name_ref = attr_expr and attr_expr.is_name() and attr_expr + name = name_ref and name_ref.name or None + + location = self.get_access_location(name) + refs = self.get_referenced_attributes(location) + + # Generate access instructions. + + subs = { + "" : str(attr_expr), + "" : str(expr), + "" : "__tmp_context", + "" : "__tmp_value", + } + + output = [] + + for instruction in self.optimiser.access_instructions[location]: + output.append(encode_access_instruction(instruction, subs)) + + out = "(\n%s\n)" % ",\n".join(output) + + del self.attrs[0] + return AttrResult(out, refs) + + def get_referenced_attributes(self, location): + + """ + Convert 'location' to the form used by the deducer and retrieve any + identified attribute. + """ + + access_location = self.deducer.const_accesses.get(location) + refs = [] + for attrtype, objpath, attr in self.deducer.referenced_attrs[access_location or location]: + refs.append(attr) + return refs + + def get_access_location(self, name): + + """ + Using the current namespace and the given 'name', return the access + location. + """ + + path = self.get_path_for_access() + + # Get the location used by the deducer and optimiser and find any + # recorded access. + + attrnames = ".".join(self.attrs) + access_number = self.get_access_number(path, name, attrnames) + self.update_access_number(path, name, attrnames) + return (path, name, attrnames, access_number) + + def get_access_number(self, path, name, attrnames): + access = name, attrnames + if self.attr_accesses.has_key(path) and self.attr_accesses[path].has_key(access): + return self.attr_accesses[path][access] + else: + return 0 + + def update_access_number(self, path, name, attrnames): + access = name, attrnames + if name: + init_item(self.attr_accesses, path, dict) + init_item(self.attr_accesses[path], access, lambda: 1) + + def process_class_node(self, n): + + "Process the given class node 'n'." + + self.enter_namespace(n.name) + + if self.have_object(): + class_name = self.get_namespace_path() + self.write_comment("Class: %s" % class_name) + + self.process_structure(n) + + self.exit_namespace() + + def process_function_body_node(self, n): + + """ + Process the given function, lambda, if expression or list comprehension + node 'n', generating the body. + """ + + function_name = self.get_namespace_path() + self.start_function(function_name) + + # Process the function body. + + self.invocation_depth = 0 + self.invocation_argument_depth = 0 + self.invocation_kw_argument_depth = 0 + + in_conditional = self.in_conditional + self.in_conditional = False + + expr = self.process_structure_node(n.code) + if expr: + self.writeline("return %s;" % expr) + + self.in_conditional = in_conditional + + self.end_function() + + def process_function_node(self, n): + + """ + Process the given function, lambda, if expression or list comprehension + node 'n', generating any initialisation statements. + """ + + # Where a function is declared conditionally, use a separate name for + # the definition, and assign the definition to the stated name. + + if self.in_conditional or self.in_function: + original_name = n.name + name = self.get_lambda_name() + else: + original_name = None + name = n.name + + # Obtain details of the defaults. + + defaults = self.process_function_defaults(n, name, self.get_object_path(name)) + if defaults: + for default in defaults: + self.writeline("%s;" % default) + + # Where a function is set conditionally, assign the name. + + if original_name: + self.process_assignment_for_function(original_name, name) + + def process_function_defaults(self, n, name, instance_name): + + """ + Process the given function or lambda node 'n', initialising defaults + that are dynamically set. The given 'name' indicates the name of the + function. The given 'instance_name' indicates the name of any separate + instance of the function created to hold the defaults. + + Return a list of operations setting defaults on a function instance. + """ + + function_name = self.get_object_path(name) + function_defaults = self.importer.function_defaults.get(function_name) + if not function_defaults: + return None + + # Determine whether any unidentified defaults are involved. + + need_defaults = [argname for argname, default in function_defaults if default.has_kind("")] + if not need_defaults: + return None + + # Where defaults are involved but cannot be identified, obtain a new + # instance of the lambda and populate the defaults. + + defaults = [] + + # Join the original defaults with the inspected defaults. + + original_defaults = [(argname, default) for (argname, default) in compiler.ast.get_defaults(n) if default] + + for i, (original, inspected) in enumerate(map(None, original_defaults, function_defaults)): + + # Obtain any reference for the default. + + if original: + argname, default = original + name_ref = self.process_structure_node(default) + elif inspected: + argname, default = inspected + name_ref = TrResolvedNameRef(argname, default) + else: + continue + + if name_ref: + defaults.append("__SETDEFAULT(%s, %s, %s)" % (encode_path(instance_name), i, name_ref)) + + return defaults + + def process_if_node(self, n): + + """ + Process the given "if" node 'n'. + """ + + first = True + for test, body in n.tests: + test_ref = self.process_structure_node(test) + self.start_if(first, test_ref) + + in_conditional = self.in_conditional + self.in_conditional = True + self.process_structure_node(body) + self.in_conditional = in_conditional + + self.end_if() + first = False + + if n.else_: + self.start_else() + self.process_structure_node(n.else_) + self.end_else() + + def process_invocation_node(self, n): + + "Process the given invocation node 'n'." + + expr = self.process_structure_node(n.node) + objpath = expr.get_origin() + + # Obtain details of the callable. + + if objpath: + parameters = self.importer.function_parameters.get(objpath) + else: + parameters = None + + stages = [] + + # Arguments are presented in a temporary frame array at the current + # position with any context always being the first argument (although it + # may be omitted for invocations where it would be unused). + + stages.append("__tmp_target = %s" % expr) + stages.append("__tmp_args[...] = __tmp_target.context") + + # Keyword arguments are positioned within the frame. + + # Defaults are added to the frame where arguments are missing. + + # The callable member of the callable is then obtained. + + if self.always_callable: + get_fn = "__load_via_object(__tmp_target, %s).fn" % \ + encode_symbol("pos", "__fn__") + else: + get_fn = "__check_and_load_via_object(__tmp_target, %s, %s).fn" % ( + encode_symbol("pos", "__fn__"), encode_symbol("code", "__fn__")) + + stages.append(get_fn) + + output = "(\n%s\n)(__tmp_frame)" % ",\n".join(stages) + + return make_expression("".join(output)) + + def always_callable(self, refs): + + "Determine whether all 'refs' are callable." + + for ref in refs: + if not ref.static(): + return False + else: + origin = ref.final() + if not self.importer.get_attribute(origin, "__fn__"): + return False + return True + + def need_default_arguments(self, objpath, nargs): + + """ + Return whether any default arguments are needed when invoking the object + given by 'objpath'. + """ + + parameters = self.importer.function_parameters.get(objpath) + return nargs < len(parameters) + + def process_lambda_node(self, n): + + "Process the given lambda node 'n'." + + name = self.get_lambda_name() + function_name = self.get_object_path(name) + + defaults = self.process_function_defaults(n, name, "__tmp") + if not defaults: + return make_expression(encode_path(function_name)) + else: + return make_expression("(__COPY(%s, __tmp), %s)" % (encode_path(function_name), ", ".join(defaults))) + + def process_logical_node(self, n): + + "Process the given operator node 'n'." + + if isinstance(n, compiler.ast.And): + op = " && " + else: + op = " || " + + # NOTE: This needs to evaluate whether the operands are true or false + # NOTE: according to Python rules. + + results = [("(%s)" % self.process_structure_node(node)) for node in n.nodes] + return make_expression("(%s)" % op.join(results)) + + def process_name_node(self, n, expr=None): + + "Process the given name node 'n' with the optional assignment 'expr'." + + # Determine whether the name refers to a static external entity. + + if n.name in predefined_constants: + return PredefinedConstantRef(n.name) + + # Convert literal references. + + elif n.name.startswith("$L"): + literal_name = n.name[len("$L"):] + ref = self.importer.get_object("__builtins__.%s" % literal_name) + return TrResolvedNameRef(n.name, ref) + + # Convert operator function names to references. + + elif n.name.startswith("$op"): + opname = n.name[len("$op"):] + ref = self.importer.get_object("operator.%s" % opname) + return TrResolvedNameRef(n.name, ref) + + # Get the appropriate name for the name reference, using the same method + # as in the inspector. + + path = self.get_object_path(n.name) + ref = self.importer.get_object(path) + name = self.get_name_for_tracking(n.name, ref and ref.final()) + + # Get the static identity of the name. + + ref = self.importer.identify(path) + + # Obtain any resolved names for non-assignment names. + + if not expr and not ref and self.in_function: + locals = self.importer.function_locals.get(self.get_namespace_path()) + ref = locals and locals.get(n.name) + + # Qualified names are used for resolved static references or for + # static namespace members. The reference should be configured to return + # such names. + + return TrResolvedNameRef(name, ref, expr=expr) + + def process_not_node(self, n): + + "Process the given operator node 'n'." + + # NOTE: This needs to evaluate whether the operand is true or false + # NOTE: according to Python rules. + + return make_expression("(!(%s))" % n.expr) + + def process_try_node(self, n): + + """ + Process the given "try...except" node 'n'. + """ + + # NOTE: Placeholders/macros. + + self.writeline("TRY") + self.writeline("{") + self.indent += 1 + self.process_structure_node(n.body) + self.indent -= 1 + self.writeline("}") + + for name, var, handler in n.handlers: + if name is not None: + name_ref = self.process_structure_node(name) + self.writeline("EXCEPT(%s)" % name_ref) + + self.writeline("{") + self.indent += 1 + + # Establish the local for the handler. + # NOTE: Need to provide the exception value. + + if var is not None: + var_ref = self.process_structure_node(var) + + if handler is not None: + self.process_structure_node(handler) + + self.indent -= 1 + self.writeline("}") + + if n.else_: + self.process_structure_node(n.else_) + + def process_try_finally_node(self, n): + + """ + Process the given "try...finally" node 'n'. + """ + + # NOTE: Placeholders/macros. + + self.writeline("TRY") + self.writeline("{") + self.indent += 1 + self.process_structure_node(n.body) + self.indent -= 1 + self.writeline("}") + self.writeline("FINALLY") + self.writeline("{") + self.indent += 1 + self.process_structure_node(n.final) + self.indent -= 1 + self.writeline("}") + + def process_while_node(self, n): + + "Process the given while node 'n'." + + self.writeline("while (1)") + self.writeline("{") + self.indent += 1 + test = self.process_structure_node(n.test) + + # Emit the loop termination condition unless "while " is + # indicated. + + if not (isinstance(test, PredefinedConstantRef) and test.value): + + # NOTE: This needs to evaluate whether the operand is true or false + # NOTE: according to Python rules. + + self.writeline("if (!(%s))" % test) + self.writeline("{") + self.indent += 1 + if n.else_: + self.process_structure_node(n.else_) + self.writeline("break;") + self.indent -= 1 + self.writeline("}") + + in_conditional = self.in_conditional + self.in_conditional = True + self.process_structure_node(n.body) + self.in_conditional = in_conditional + + self.indent -= 1 + self.writeline("}") + + # Output generation. + + def start_module(self): + print >>self.out, "void __main_%s()" % encode_path(self.name) + print >>self.out, "{" + self.indent += 1 + self.emit_invocation_storage(self.name) + + def end_module(self): + self.indent -= 1 + self.end_function() + + def start_function(self, name): + print >>self.out, "__attr %s(__attr __args[])" % encode_function_pointer(name) + print >>self.out, "{" + self.indent += 1 + + # Obtain local names from parameters. + + parameters = self.importer.function_parameters[name] + names = [] + locals = self.importer.function_locals[name].keys() + + for n in locals: + + # Filter out special names and parameters. Note that self is a local + # regardless of whether it originally appeared in the parameters or + # not. + + if n.startswith("$l") or n in parameters or n == "self": + continue + names.append(encode_path(n)) + + # Emit required local names. + + if names: + names.sort() + self.writeline("__attr %s;" % ", ".join(names)) + + self.emit_invocation_storage(name) + + # Generate any self reference. + + if self.in_method(name): + self.writeline("#define self (__args[0])") + + # Generate aliases for the parameters. + + for i, parameter in enumerate(parameters): + self.writeline("#define %s (__args[%d])" % (encode_path(parameter), i+1)) + + def end_function(self): + self.indent -= 1 + print >>self.out, "}" + + def start_if(self, first, test_ref): + + # NOTE: This needs to evaluate whether the operand is true or false + # NOTE: according to Python rules. + + self.writeline("%sif (%s)" % (not first and "else " or "", test_ref)) + self.writeline("{") + self.indent += 1 + + def end_if(self): + self.indent -= 1 + self.writeline("}") + + def start_else(self): + self.writeline("else") + self.writeline("{") + self.indent += 1 + + def end_else(self): + self.indent -= 1 + self.writeline("}") + + def emit_invocation_storage(self, name): + + "Emit invocation temporary storage." + + if self.importer.function_targets.has_key(name): + self.writeline("__attr __tmp_targets[%d];" % self.importer.function_targets[name]) + + if self.importer.function_arguments.has_key(name): + self.writeline("__attr __tmp_args[%d];" % self.importer.function_arguments[name]) + + def statement(self, expr): + # NOTE: Should never be None. + if not expr: + self.writeline("...;") + s = str(expr) + if s: + self.writeline("%s;" % s) + + def statements(self, results): + for result in results: + self.statement(result) + + def pad(self, extra=0): + return (self.indent + extra) * self.tabstop + + def indenttext(self, s, levels): + return s.replace("\n", "\n%s" % (levels * self.tabstop)) + + def writeline(self, s): + print >>self.out, "%s%s" % (self.pad(), self.indenttext(s, self.indent + 1)) + + def write_comment(self, s): + self.writeline("/* %s */" % s) + +# vim: tabstop=4 expandtab shiftwidth=4