Robert Bradshaw wrote:
This looks very, very interesting and promising. I was thinking about code transformations like this quite a while ago for optimization reasons. I think this is the way to go.
In that case, I've cleaned it up a little bit and now submit it as a patch against the current cython-devel repo (see attachment); I believe it is ready for inclusion in the cython-devel branch if you approve.

It doesn't *do* anything currently, and really shouldn't affect behaviour if one doesn't use the new -T switch (so regression testing for this patch can be done simply by checking that the same code is generated for a lot of examples -- I didn't take time for this though).

However placing it in the main repository means that more people than myself can start playing with applying transforms that actually does something.

Changes:

A little code has been added all over the place, but most of it doesn't "interact" at all with existing code, unless you count declaring new symbols. The diff is small enough that you can simply read it .. the "worst" parts:

- Node.generate_function_definitions got an extra argument everywhere (but I grepped for usages and am pretty sure I fixed all calls, at least within the Compiler module).

- FuncDefNode.generate_function_definitions has calls to the transforms added:

        self.declare_arguments(lenv)
+ transforms.run('before_analyse_function', self, env=env, lenv=lenv, genv=genv)
        self.body.analyse_declarations(lenv)
        self.body.analyse_expressions(lenv)
+ transforms.run('after_analyse_function', self, env=env, lenv=lenv, genv=genv)

(Without program arguments, these will iterate an empty list and return.)

Known problems:
- Only two cut-points/phases for transforms are provided, and more are neeeded, especially one in-between type deduction and type coercion, but this requires more intrusive refactoring that is better left for a seperate commit. This gives a start though. - The lists of which attributes for each node constitute "children" in the tree can probably be tuned a bit, however this is best left for when one actually have a use-case (transform) making use of the node type in question.


Dag Sverre
diff -r 48cbf2bbbd7f Compiler/CmdLine.py
--- a/Compiler/CmdLine.py	Thu Mar 06 11:39:10 2008 +0100
+++ b/Compiler/CmdLine.py	Sat Mar 08 13:33:42 2008 +0100
@@ -4,6 +4,7 @@
 
 import sys
 import Options
+import Transform
 
 usage = """\
 Cython (http://cython.org) is a compiler for code written in the
@@ -36,11 +37,36 @@ Options:
 #  -+, --cplus      Use C++ compiler for compiling and linking
 #  Additional .o files to link may be supplied when using -X."""
 
+#The following options are very experimental and is used for plugging in code
+#into different transform stages.
+#  -T phase:factory At the phase given, hand off the tree to the transform returned
+#                   when calling factory without arguments. Factory should be fully
+#                   specified (ie Module.SubModule.factory) and the containing module
+#                   will be imported. This option can be repeated to add more transforms,
+#                   transforms for the same phase will be used in the order they are given.
+
 def bad_usage():
     print >>sys.stderr, usage
     sys.exit(1)
 
 def parse_command_line(args):
+
+    def parse_add_transform(transforms, param):
+        def import_symbol(fqn):
+            modsplitpt = fqn.rfind(".")
+            if modsplitpt == -1: bad_usage()
+            modulename = fqn[:modsplitpt]
+            symbolname = fqn[modsplitpt+1:]
+            module = __import__(modulename, fromlist=[symbolname], level=0)
+            return getattr(module, symbolname)
+    
+        stagename, factoryname = param.split(":")
+        if not stagename in Transform.PHASES:
+            bad_usage()
+        factory = import_symbol(factoryname)
+        transform = factory()
+        transforms[stagename].append(transform)
+    
     from Cython.Compiler.Main import \
         CompilationOptions, default_options
 
@@ -93,6 +119,9 @@ def parse_command_line(args):
                 Options.annotate = True
             elif option == "--convert-range":
                 Options.convert_range = True
+            elif option.startswith("-T"):
+                parse_add_transform(options.transforms, get_param(option))
+                # Note: this can occur multiple times, each time appends
             else:
                 bad_usage()
         else:
diff -r 48cbf2bbbd7f Compiler/ExprNodes.py
--- a/Compiler/ExprNodes.py	Thu Mar 06 11:39:10 2008 +0100
+++ b/Compiler/ExprNodes.py	Sat Mar 08 13:33:42 2008 +0100
@@ -31,6 +31,7 @@ class ExprNode(Node):
     #                            Cached result of subexpr_nodes()
     
     result_ctype = None
+    type = None
 
     #  The Analyse Expressions phase for expressions is split
     #  into two sub-phases:
@@ -165,6 +166,14 @@ class ExprNode(Node):
     saved_subexpr_nodes = None
     is_temp = 0
 
+    def get_child_attrs(self):
+        """Automatically provide the contents of subexprs as children, unless child_attr
+        has been declared. See Nodes.Node.get_child_accessors."""
+        if self.child_attrs != None:
+            return self.child_attr
+        elif self.subexprs != None:
+            return self.subexprs
+        
     def not_implemented(self, method_name):
         print_call_chain(method_name, "not implemented") ###
         raise InternalError(
diff -r 48cbf2bbbd7f Compiler/Main.py
--- a/Compiler/Main.py	Thu Mar 06 11:39:10 2008 +0100
+++ b/Compiler/Main.py	Sat Mar 08 13:33:42 2008 +0100
@@ -18,6 +18,7 @@ import Code
 import Code
 from Cython.Utils import replace_suffix
 from Cython import Utils
+import Transform
 
 verbose = 0
 
@@ -236,6 +237,7 @@ class CompilationOptions:
     include_path      [string]  Directories to search for include files
     output_file       string    Name of generated .c file
     generate_pxi      boolean   Generate .pxi file for public declarations
+    transforms        Transform.TransformSet Transforms to use on the parse tree
     
     Following options are experimental and only used on MacOSX:
     
@@ -342,7 +344,8 @@ default_options = dict(
     obj_only = 1,
     cplus = 0,
     output_file = None,
-    generate_pxi = 0)
+    generate_pxi = 0,
+    transforms = Transform.TransformSet())
     
 if sys.platform == "mac":
     from Cython.Mac.MacSystem import c_compile, c_link, CCompilerError
diff -r 48cbf2bbbd7f Compiler/ModuleNode.py
--- a/Compiler/ModuleNode.py	Thu Mar 06 11:39:10 2008 +0100
+++ b/Compiler/ModuleNode.py	Sat Mar 08 13:33:42 2008 +0100
@@ -26,6 +26,8 @@ class ModuleNode(Nodes.Node, Nodes.Block
     #  referenced_modules   [ModuleScope]
     #  module_temp_cname    string
     #  full_module_name     string
+
+    children_attrs = ["body"]
     
     def analyse_declarations(self, env):
         if Options.embed_pos_in_docstring:
@@ -46,7 +48,7 @@ class ModuleNode(Nodes.Node, Nodes.Block
         if self.has_imported_c_functions():
             self.module_temp_cname = env.allocate_temp_pyobject()
             env.release_temp(self.module_temp_cname)
-        self.generate_c_code(env, result)
+        self.generate_c_code(env, options, result)
         self.generate_h_code(env, options, result)
         self.generate_api_code(env, result)
     
@@ -199,7 +201,7 @@ class ModuleNode(Nodes.Node, Nodes.Block
             i_code.putln("pass")
         i_code.dedent()
     
-    def generate_c_code(self, env, result):
+    def generate_c_code(self, env, options, result):
         modules = self.referenced_modules
         if Options.annotate:
             code = Annotate.AnnotationCCodeWriter(StringIO())
@@ -216,7 +218,7 @@ class ModuleNode(Nodes.Node, Nodes.Block
         self.generate_interned_name_decls(env, code)
         self.generate_py_string_decls(env, code)
         self.generate_cached_builtins_decls(env, code)
-        self.body.generate_function_definitions(env, code)
+        self.body.generate_function_definitions(env, code, options.transforms)
         code.mark_pos(None)
         self.generate_interned_name_table(env, code)
         self.generate_py_string_table(env, code)
diff -r 48cbf2bbbd7f Compiler/Nodes.py
--- a/Compiler/Nodes.py	Thu Mar 06 11:39:10 2008 +0100
+++ b/Compiler/Nodes.py	Sat Mar 08 13:33:42 2008 +0100
@@ -38,6 +38,21 @@ def relative_position(pos):
     return (pos[0][absolute_path_length+1:], pos[1])
         
 
+class AttributeAccessor:
+    """Used as the result of the Node.get_children_accessors() generator"""
+    def __init__(self, obj, attrname):
+        self.obj = obj
+        self.attrname = attrname
+    def get(self):
+        try:
+            return getattr(self.obj, self.attrname)
+        except AttributeError:
+            return None
+    def set(self, value):
+        setattr(self.obj, self.attrname, value)
+    def name(self):
+        return self.attrname
+
 class Node:
     #  pos         (string, int, int)   Source file position
     #  is_name     boolean              Is a NameNode
@@ -45,10 +60,63 @@ class Node:
     
     is_name = 0
     is_literal = 0
+
+    # All descandants should set child_attrs (see get_child_accessors)    
+    child_attrs = None
     
     def __init__(self, pos, **kw):
         self.pos = pos
         self.__dict__.update(kw)
+    
+    def get_child_accessors(self):
+        """Returns an iterator over the children of the Node. Each member in the
+        iterated list is an object with get(), set(value), and name() methods,
+        which can be used to fetch and replace the child and query the name
+        the relation this node has with the child. For instance, for an
+        assignment node, this code:
+        
+        for child in assignment_node.get_child_accessors():
+            print(child.name())
+            child.set(i_node)
+        
+        will print "lhs", "rhs", and change the assignment statement to "i = i"
+        (assuming that i_node is a node able to represent the variable i in the
+        tree).
+        
+        Any kind of objects can in principle be returned, but the typical
+        candidates are either Node instances or lists of node instances.
+        
+        The object returned in each iteration stage can only be used until the
+        iterator is advanced to the next child attribute. (However, the objects
+        returned by the get() function can be kept).
+        
+        Typically, a Node instance will have other interesting and potentially
+        hierarchical attributes as well. These must be explicitly accessed -- this
+        method only provides access to attributes that are deemed to naturally
+        belong in the parse tree.
+        
+        Descandant classes can either specify child_attrs, override get_child_attrs,
+        or override this method directly in order to provide access to their
+        children. All descendants of Node *must* declare their children -- leaf nodes
+        should simply declare "child_attrs = []".
+        """
+        attrnames = self.get_child_attrs()
+        if attrnames is None:
+            raise InternalError("Children access not implemented for %s" % \
+                self.__class__.__name__)
+        for name in attrnames:
+            a = AttributeAccessor(self, name)
+            yield a
+            # Not really needed, but one wants to enforce clients not to
+            # hold on to iterated objects, in case more advanced iteration
+            # is needed later
+            a.attrname = None
+    
+    def get_child_attrs(self):
+        """Utility method for more easily implementing get_child_accessors.
+        If you override get_child_accessors then this method is not used."""
+        return self.child_attrs
+    
     
     #
     #  There are 3 phases of parse tree processing, applied in order to
@@ -145,6 +213,8 @@ class StatListNode(Node):
 class StatListNode(Node):
     # stats     a list of StatNode
     
+    child_attrs = ["stats"]
+    
     def analyse_declarations(self, env):
         #print "StatListNode.analyse_declarations" ###
         for stat in self.stats:
@@ -155,10 +225,10 @@ class StatListNode(Node):
         for stat in self.stats:
             stat.analyse_expressions(env)
     
-    def generate_function_definitions(self, env, code):
+    def generate_function_definitions(self, env, code, transforms):
         #print "StatListNode.generate_function_definitions" ###
         for stat in self.stats:
-            stat.generate_function_definitions(env, code)
+            stat.generate_function_definitions(env, code, transforms)
             
     def generate_execution_code(self, code):
         #print "StatListNode.generate_execution_code" ###
@@ -184,7 +254,7 @@ class StatNode(Node):
     #        Emit C code for executable statements.
     #
     
-    def generate_function_definitions(self, env, code):
+    def generate_function_definitions(self, env, code, transforms):
         pass
     
     def generate_execution_code(self, code):
@@ -195,6 +265,8 @@ class CDefExternNode(StatNode):
 class CDefExternNode(StatNode):
     #  include_file   string or None
     #  body           StatNode
+    
+    child_attrs = ["body"]
     
     def analyse_declarations(self, env):
         if self.include_file:
@@ -227,6 +299,8 @@ class CDeclaratorNode(Node):
     #  calling_convention  string   Calling convention of CFuncDeclaratorNode
     #                               for which this is a base 
 
+    child_attrs = []
+
     calling_convention = ""
 
     def analyse_expressions(self, env):
@@ -240,6 +314,8 @@ class CNameDeclaratorNode(CDeclaratorNod
     #  name   string           The Pyrex name being declared
     #  cname  string or None   C name, if specified
     
+    child_attrs = []
+
     def analyse(self, base_type, env):
         self.type = base_type
         return self, base_type
@@ -268,6 +344,8 @@ class CPtrDeclaratorNode(CDeclaratorNode
 class CPtrDeclaratorNode(CDeclaratorNode):
     # base     CDeclaratorNode
     
+    child_attrs = ["base"]
+
     def analyse(self, base_type, env):
         if base_type.is_pyobject:
             error(self.pos,
@@ -284,6 +362,8 @@ class CArrayDeclaratorNode(CDeclaratorNo
 class CArrayDeclaratorNode(CDeclaratorNode):
     # base        CDeclaratorNode
     # dimension   ExprNode
+
+    child_attrs = ["base", "dimension"]
     
     def analyse(self, base_type, env):
         if self.dimension:
@@ -315,6 +395,8 @@ class CFuncDeclaratorNode(CDeclaratorNod
     # nogil            boolean    Can be called without gil
     # with_gil         boolean    Acquire gil around function body
     
+    child_attrs = ["base", "args", "exception_value"]
+
     overridable = 0
     optional_arg_count = 0
 
@@ -403,6 +485,8 @@ class CArgDeclNode(Node):
     # is_self_arg    boolean            Is the "self" arg of an extension type method
     # is_kw_only     boolean            Is a keyword-only argument
 
+    child_attrs = ["base_type", "declarator", "default"]
+
     is_self_arg = 0
     is_generic = 1
 
@@ -435,6 +519,8 @@ class CSimpleBaseTypeNode(CBaseTypeNode)
     # longness         integer
     # is_self_arg      boolean      Is self argument of C method
 
+    child_attrs = []
+    
     def analyse(self, env):
         # Return type descriptor.
         #print "CSimpleBaseTypeNode.analyse: is_self_arg =", self.is_self_arg ###
@@ -480,6 +566,8 @@ class CComplexBaseTypeNode(CBaseTypeNode
     # base_type   CBaseTypeNode
     # declarator  CDeclaratorNode
     
+    child_attrs = ["base_type", "declarator"]
+
     def analyse(self, env):
         base = self.base_type.analyse(env)
         _, type = self.declarator.analyse(base, env)
@@ -494,6 +582,8 @@ class CVarDefNode(StatNode):
     #  declarators   [CDeclaratorNode]
     #  in_pxd        boolean
     #  api           boolean
+
+    child_attrs = ["base_type", "declarators"]
     
     def analyse_declarations(self, env, dest_scope = None):
         if not dest_scope:
@@ -543,6 +633,8 @@ class CStructOrUnionDefNode(StatNode):
     #  attributes    [CVarDefNode] or None
     #  entry         Entry
     
+    child_attrs = ["attributes"]
+
     def analyse_declarations(self, env):
         scope = None
         if self.attributes is not None:
@@ -572,6 +664,8 @@ class CEnumDefNode(StatNode):
     #  in_pxd         boolean
     #  entry          Entry
     
+    child_attrs = ["items"]
+    
     def analyse_declarations(self, env):
         self.entry = env.declare_enum(self.name, self.pos,
             cname = self.cname, typedef_flag = self.typedef_flag,
@@ -594,6 +688,8 @@ class CEnumDefItemNode(StatNode):
     #  cname    string or None
     #  value    ExprNode or None
     
+    child_attrs = ["value"]
+
     def analyse_declarations(self, env, enum_entry):
         if self.value:
             self.value.analyse_const_expression(env)
@@ -610,6 +706,8 @@ class CTypeDefNode(StatNode):
     #  declarator   CDeclaratorNode
     #  visibility   "public" or "private"
     #  in_pxd       boolean
+
+    child_attrs = ["base_type", "declarator"]
     
     def analyse_declarations(self, env):
         base = self.base_type.analyse(env)
@@ -663,7 +761,7 @@ class FuncDefNode(StatNode, BlockNode):
     def need_gil_acquisition(self, lenv):
         return 0
                 
-    def generate_function_definitions(self, env, code):
+    def generate_function_definitions(self, env, code, transforms):
         code.mark_pos(self.pos)
         # Generate C code for header and body of function
         genv = env.global_scope()
@@ -671,8 +769,10 @@ class FuncDefNode(StatNode, BlockNode):
         lenv.return_type = self.return_type
         code.init_labels()
         self.declare_arguments(lenv)
+        transforms.run('before_analyse_function', self, env=env, lenv=lenv, genv=genv)
         self.body.analyse_declarations(lenv)
         self.body.analyse_expressions(lenv)
+        transforms.run('after_analyse_function', self, env=env, lenv=lenv, genv=genv)
         # Code for nested function definitions would go here
         # if we supported them, which we probably won't.
         # ----- Top-level constants used by this function
@@ -775,7 +875,7 @@ class FuncDefNode(StatNode, BlockNode):
         code.putln("}")
         # ----- Python version
         if self.py_func:
-            self.py_func.generate_function_definitions(env, code)
+            self.py_func.generate_function_definitions(env, code, transforms)
         self.generate_optarg_wrapper_function(env, code)
 
     def put_stararg_decrefs(self, code):
@@ -834,6 +934,8 @@ class CFuncDefNode(FuncDefNode):
     #  with_gil      boolean    Acquire GIL around body
     #  type          CFuncType
     
+    child_attrs = ["base_type", "declarator", "body"]
+
     def unqualified_name(self):
         return self.entry.name
         
@@ -1032,6 +1134,7 @@ class PyArgDeclNode(Node):
     #
     # name   string
     # entry  Symtab.Entry
+    child_attrs = []
     
     pass
     
@@ -1051,6 +1154,8 @@ class DefNode(FuncDefNode):
     #
     #  assmt   AssignmentNode   Function construction/assignment
     
+    child_attrs = ["args", "star_arg", "starstar_arg", "body"]
+
     assmt = None
     num_kwonly_args = 0
     num_required_kw_args = 0
@@ -1739,6 +1844,8 @@ class PyClassDefNode(StatNode, BlockNode
     #  dict     DictNode   Class dictionary
     #  classobj ClassNode  Class object
     #  target   NameNode   Variable to assign class object to
+
+    child_attrs = ["body", "dict", "classobj", "target"]
     
     def __init__(self, pos, name, bases, doc, body):
         StatNode.__init__(self, pos)
@@ -1777,10 +1884,10 @@ class PyClassDefNode(StatNode, BlockNode
         #self.classobj.release_temp(env)
         #self.target.release_target_temp(env)
     
-    def generate_function_definitions(self, env, code):
+    def generate_function_definitions(self, env, code, transforms):
         self.generate_py_string_decls(self.scope, code)
         self.body.generate_function_definitions(
-            self.scope, code)
+            self.scope, code, transforms)
     
     def generate_execution_code(self, code):
         self.dict.generate_evaluation_code(code)
@@ -1809,6 +1916,8 @@ class CClassDefNode(StatNode):
     #  entry              Symtab.Entry
     #  base_type          PyExtensionType or None
     
+    child_attrs = ["body"]
+
     def analyse_declarations(self, env):
         #print "CClassDefNode.analyse_declarations:", self.class_name
         #print "...visibility =", self.visibility
@@ -1880,10 +1989,10 @@ class CClassDefNode(StatNode):
             scope = self.entry.type.scope
             self.body.analyse_expressions(scope)
     
-    def generate_function_definitions(self, env, code):
+    def generate_function_definitions(self, env, code, transforms):
         if self.body:
             self.body.generate_function_definitions(
-                self.entry.type.scope, code)
+                self.entry.type.scope, code, transforms)
     
     def generate_execution_code(self, code):
         # This is needed to generate evaluation code for
@@ -1903,6 +2012,8 @@ class PropertyNode(StatNode):
     #  doc    string or None    Doc string
     #  body   StatListNode
     
+    child_attrs = ["body"]
+
     def analyse_declarations(self, env):
         entry = env.declare_property(self.name, self.doc, self.pos)
         if entry:
@@ -1914,8 +2025,8 @@ class PropertyNode(StatNode):
     def analyse_expressions(self, env):
         self.body.analyse_expressions(env)
     
-    def generate_function_definitions(self, env, code):
-        self.body.generate_function_definitions(env, code)
+    def generate_function_definitions(self, env, code, transforms):
+        self.body.generate_function_definitions(env, code, transforms)
 
     def generate_execution_code(self, code):
         pass
@@ -1929,6 +2040,8 @@ class GlobalNode(StatNode):
     #
     # names    [string]
     
+    child_attrs = []
+
     def analyse_declarations(self, env):
         for name in self.names:
             env.declare_global(name, self.pos)
@@ -1944,6 +2057,8 @@ class ExprStatNode(StatNode):
     #  Expression used as a statement.
     #
     #  expr   ExprNode
+
+    child_attrs = ["expr"]
     
     def analyse_expressions(self, env):
         self.expr.analyse_expressions(env)
@@ -1989,6 +2104,8 @@ class SingleAssignmentNode(AssignmentNod
     #
     #  lhs      ExprNode      Left hand side
     #  rhs      ExprNode      Right hand side
+    
+    child_attrs = ["lhs", "rhs"]
 
     def analyse_declarations(self, env):
         self.lhs.analyse_target_declaration(env)
@@ -2044,6 +2161,8 @@ class CascadedAssignmentNode(AssignmentN
     #
     #  coerced_rhs_list   [ExprNode]   RHS coerced to type of each LHS
     
+    child_attrs = ["lhs_list", "rhs", "coerced_rhs_list"]
+
     def analyse_declarations(self, env):
         for lhs in self.lhs_list:
             lhs.analyse_target_declaration(env)
@@ -2128,6 +2247,8 @@ class ParallelAssignmentNode(AssignmentN
     #
     #  stats     [AssignmentNode]   The constituent assignments
     
+    child_attrs = ["stats"]
+
     def analyse_declarations(self, env):
         for stat in self.stats:
             stat.analyse_declarations(env)
@@ -2175,6 +2296,8 @@ class InPlaceAssignmentNode(AssignmentNo
     #  Fortunately, the type of the lhs node is fairly constrained 
     #  (it must be a NameNode, AttributeNode, or IndexNode).     
     
+    child_attrs = ["lhs", "rhs", "dup"]
+
     def analyse_declarations(self, env):
         self.lhs.analyse_target_declaration(env)
         
@@ -2272,6 +2395,8 @@ class PrintStatNode(StatNode):
     #  args              [ExprNode]
     #  ends_with_comma   boolean
     
+    child_attrs = ["args"]
+    
     def analyse_expressions(self, env):
         for i in range(len(self.args)):
             arg = self.args[i]
@@ -2306,6 +2431,8 @@ class DelStatNode(StatNode):
     #
     #  args     [ExprNode]
     
+    child_attrs = ["args"]
+
     def analyse_declarations(self, env):
         for arg in self.args:
             arg.analyse_target_declaration(env)
@@ -2330,6 +2457,8 @@ class DelStatNode(StatNode):
 
 class PassStatNode(StatNode):
     #  pass statement
+
+    child_attrs = []
     
     def analyse_expressions(self, env):
         pass
@@ -2339,6 +2468,8 @@ class PassStatNode(StatNode):
 
 
 class BreakStatNode(StatNode):
+
+    child_attrs = []
 
     def analyse_expressions(self, env):
         pass
@@ -2354,6 +2485,8 @@ class BreakStatNode(StatNode):
 
 
 class ContinueStatNode(StatNode):
+
+    child_attrs = []
 
     def analyse_expressions(self, env):
         pass
@@ -2377,6 +2510,8 @@ class ReturnStatNode(StatNode):
     #  return_type   PyrexType
     #  temps_in_use  [Entry]            Temps in use at time of return
     
+    child_attrs = ["value"]
+
     def analyse_expressions(self, env):
         return_type = env.return_type
         self.return_type = return_type
@@ -2439,6 +2574,8 @@ class RaiseStatNode(StatNode):
     #  exc_value   ExprNode or None
     #  exc_tb      ExprNode or None
     
+    child_attrs = ["exc_type", "exc_value", "exc_tb"]
+
     def analyse_expressions(self, env):
         if self.exc_type:
             self.exc_type.analyse_types(env)
@@ -2508,6 +2645,8 @@ class RaiseStatNode(StatNode):
 
 class ReraiseStatNode(StatNode):
 
+    child_attrs = []
+
     def analyse_expressions(self, env):
         env.use_utility_code(raise_utility_code)
 
@@ -2526,6 +2665,8 @@ class AssertStatNode(StatNode):
     #  cond    ExprNode
     #  value   ExprNode or None
     
+    child_attrs = ["cond", "value"]
+
     def analyse_expressions(self, env):
         self.cond = self.cond.analyse_boolean_expression(env)
         if self.value:
@@ -2570,6 +2711,8 @@ class IfStatNode(StatNode):
     #
     #  if_clauses   [IfClauseNode]
     #  else_clause  StatNode or None
+
+    child_attrs = ["if_clauses", "else_clause"]
     
     def analyse_declarations(self, env):
         for if_clause in self.if_clauses:
@@ -2607,6 +2750,8 @@ class IfClauseNode(Node):
     #  condition   ExprNode
     #  body        StatNode
     
+    child_attrs = ["condition", "body"]
+
     def analyse_declarations(self, env):
         self.condition.analyse_declarations(env)
         self.body.analyse_declarations(env)
@@ -2641,6 +2786,8 @@ class WhileStatNode(StatNode):
     #  condition    ExprNode
     #  body         StatNode
     #  else_clause  StatNode
+
+    child_attrs = ["condition", "body", "else_clause"]
     
     def analyse_declarations(self, env):
         self.body.analyse_declarations(env)
@@ -2696,6 +2843,8 @@ class ForInStatNode(StatNode):
     #  body          StatNode
     #  else_clause   StatNode
     #  item          NextNode       used internally
+    
+    child_attrs = ["target", "iterator", "body", "else_clause", "item"]
     
     def analyse_declarations(self, env):
         self.target.analyse_target_declaration(env)
@@ -2811,6 +2960,7 @@ class ForFromStatNode(StatNode):
     #  is_py_target       bool
     #  loopvar_name       string
     #  py_loopvar_node    PyTempNode or None
+    child_attrs = ["target", "bound1", "bound2", "step", "body", "else_clause", "py_loopvar_node"]
     
     def analyse_declarations(self, env):
         self.target.analyse_target_declaration(env)
@@ -2933,6 +3083,8 @@ class TryExceptStatNode(StatNode):
     #  except_clauses   [ExceptClauseNode]
     #  else_clause      StatNode or None
     #  cleanup_list     [Entry]            temps to clean up on error
+
+    child_attrs = ["body", "except_clauses", "else_clause", "cleanup_list"]
     
     def analyse_declarations(self, env):
         self.body.analyse_declarations(env)
@@ -2999,6 +3151,8 @@ class ExceptClauseNode(Node):
     #  function_name  string             qualified name of enclosing function
     #  exc_vars       (string * 3)       local exception variables
     
+    child_attrs = ["pattern", "target", "body", "exc_value"]
+
     def analyse_declarations(self, env):
         if self.target:
             self.target.analyse_target_declaration(env)
@@ -3084,6 +3238,8 @@ class TryFinallyStatNode(StatNode):
     #  In addition, if we're doing an error, we save the
     #  exception on entry to the finally block and restore
     #  it on exit.
+
+    child_attrs = ["body", "finally_clause", "cleanup_list"]
     
     preserve_exception = 1
     
@@ -3243,6 +3399,8 @@ class GILStatNode(TryFinallyStatNode):
     #
     #   state   string   'gil' or 'nogil'
         
+    child_attrs = []
+    
     preserve_exception = 0
 
     def __init__(self, pos, state, body):
@@ -3280,6 +3438,8 @@ class GILExitNode(StatNode):
     #
     #  state   string   'gil' or 'nogil'
 
+    child_attrs = []
+
     def analyse_expressions(self, env):
         pass
 
@@ -3295,6 +3455,8 @@ class CImportStatNode(StatNode):
     #
     #  module_name   string           Qualified name of module being imported
     #  as_name       string or None   Name specified in "as" clause, if any
+
+    child_attrs = []
     
     def analyse_declarations(self, env):
         if not env.is_module_scope:
@@ -3331,6 +3493,8 @@ class FromCImportStatNode(StatNode):
     #  module_name     string                  Qualified name of module
     #  imported_names  [(pos, name, as_name)]  Names to be imported
     
+    child_attrs = []
+
     def analyse_declarations(self, env):
         if not env.is_module_scope:
             error(self.pos, "cimport only allowed at module level")
@@ -3357,6 +3521,8 @@ class FromImportStatNode(StatNode):
     #  items            [(string, NameNode)]
     #  interned_items   [(string, NameNode)]
     #  item             PyTempNode            used internally
+
+    child_attrs = ["module"]
     
     def analyse_declarations(self, env):
         for _, target in self.items:
diff -r 48cbf2bbbd7f Compiler/Transform.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Compiler/Transform.py	Sat Mar 08 13:33:42 2008 +0100
@@ -0,0 +1,87 @@
+import Nodes
+import ExprNodes
+
+class Transform(object):
+    def __init__(self):
+        self.parent_stack = []
+    
+    def initialize(self, phase, **options):
+        pass
+
+    def process_children(self, node):
+        if node == None: return
+        
+        self.parent_stack.append(node)
+        for childacc in node.get_child_accessors():
+            child = childacc.get()
+            if isinstance(child, list):
+                newchild = self.process_list(child, childacc.name())
+                if not isinstance(newchild, list): raise Exception("Cannot replace list with non-list!")
+            else:
+                newchild = self.process_node(child, childacc.name())
+                if newchild is not None and not isinstance(newchild, Nodes.Node):
+                    raise Exception("Cannot replace Node with non-Node!")
+            childacc.set(newchild)
+        self.parent_stack.pop()
+
+    def process_list(self, l, name):
+        for idx in xrange(len(l)):
+            l[idx] = self.process_node(l[idx], "[%d]" % idx)
+        return l
+
+    def process_node(self, node, name):
+        return node
+
+class PrintTree(Transform):
+    def __init__(self):
+        Transform.__init__(self)
+        self._indent = ""
+
+    def indent(self):
+        self._indent += "  "
+    def unindent(self):
+        self._indent = self._indent[:-2]
+
+    def initialize(self, phase, **options):
+        print "Parse tree dump at phase '%s'" % phase
+    
+    def process_list(self, l, name):
+        print("%s- %s: (nodelist)" % (self._indent, name))
+        self.indent()
+        Transform.process_list(self, l, name)
+        self.unindent()
+        return l
+
+    def process_node(self, node, name):
+        print("%s- %s: %s" % (self._indent, name, self.repr_of(node)))
+        self.indent()
+        self.process_children(node)
+        self.unindent()
+        return node
+
+    def repr_of(self, node):
+        if node is None:
+            return "(none)"
+        else:
+            result = node.__class__.__name__
+            if isinstance(node, ExprNodes.ExprNode):
+                t = node.type
+                result += "(type=%s)" % repr(t)
+            return result
+
+
+PHASES = [
+    'before_analyse_function', # run in FuncDefNode.generate_function_definitions
+    'after_analyse_function'   # run in FuncDefNode.generate_function_definitions
+]
+
+class TransformSet(dict):
+    def __init__(self):
+        self.update([(name, []) for name in PHASES])
+    def run(self, name, node, **options):
+        assert name in self
+        for transform in self[name]:
+            transform.initialize(phase=name, **options)
+            transform.process_node(node, "(root)")
+
+
_______________________________________________
Cython-dev mailing list
[email protected]
http://codespeak.net/mailman/listinfo/cython-dev

Reply via email to