changeset 794:ada9d53ea751

Merged AST branch back into trunk. Most of this code was written by Marcin Kurczych for his Google Summer of Code 2008 project. The merge of this branch means that Genshi now uses the native `_ast` module on Python >= 2.5, and an emulation thereof on Python 2.4. This replaces the usage of the `compiler` package, which was deprecated in Python 2.6 and removed in Python 3.0. Another effect is that Genshi now runs on Google AppEngine (although performance is bad due to the lack of template caching).
author cmlenz
date Tue, 16 Dec 2008 23:02:36 +0000
parents 7cf2407671c2
children d282c6c0133e
files genshi/filters/i18n.py genshi/template/_ast24.py genshi/template/ast24.py genshi/template/astgae.py genshi/template/astutil.py genshi/template/directives.py genshi/template/eval.py scripts/ast_generator.py
diffstat 8 files changed, 2075 insertions(+), 389 deletions(-) [+]
line wrap: on
line diff
--- a/genshi/filters/i18n.py
+++ b/genshi/filters/i18n.py
@@ -16,13 +16,13 @@
 :since: version 0.4
 """
 
-from compiler import ast
 from gettext import NullTranslations
 import re
 from types import FunctionType
 
 from genshi.core import Attrs, Namespace, QName, START, END, TEXT, START_NS, \
                         END_NS, XML_NAMESPACE, _ensure
+from genshi.template.eval import _ast
 from genshi.template.base import DirectiveFactory, EXPR, SUB, _apply_directives
 from genshi.template.directives import Directive
 from genshi.template.markup import MarkupTemplate, EXEC
@@ -526,25 +526,32 @@
     :since: version 0.5
     """
     def _walk(node):
-        if isinstance(node, ast.CallFunc) and isinstance(node.node, ast.Name) \
-                and node.node.name in gettext_functions:
+        if isinstance(node, _ast.Call) and isinstance(node.func, _ast.Name) \
+                and node.func.id in gettext_functions:
             strings = []
             def _add(arg):
-                if isinstance(arg, ast.Const) \
-                        and isinstance(arg.value, basestring):
-                    strings.append(unicode(arg.value, 'utf-8'))
-                elif arg and not isinstance(arg, ast.Keyword):
+                if isinstance(arg, _ast.Str) and isinstance(arg.s, basestring):
+                    strings.append(unicode(arg.s, 'utf-8'))
+                elif arg:
                     strings.append(None)
             [_add(arg) for arg in node.args]
-            _add(node.star_args)
-            _add(node.dstar_args)
+            _add(node.starargs)
+            _add(node.kwargs)
             if len(strings) == 1:
                 strings = strings[0]
             else:
                 strings = tuple(strings)
-            yield node.node.name, strings
-        else:
-            for child in node.getChildNodes():
+            yield node.func.id, strings
+        elif node._fields:
+            children = []
+            for field in node._fields:
+                child = getattr(node, field, None)
+                if isinstance(child, list):
+                    for elem in child:
+                        children.append(elem)
+                elif isinstance(child, _ast.AST):
+                    children.append(child)
+            for child in children:
                 for funcname, strings in _walk(child):
                     yield funcname, strings
     return _walk(code.ast)
new file mode 100644
--- /dev/null
+++ b/genshi/template/_ast24.py
@@ -0,0 +1,446 @@
+# Generated automatically, please do not edit
+# Generator can be found in Genshi SVN, scripts/ast-generator.py
+
+__version__ = 43614
+
+class AST(object):
+	_fields = None
+	__doc__ = None
+
+class operator(AST):
+	_fields = None
+	__doc__ = None
+	_attributes = []
+class Add(operator):
+	_fields = None
+	__doc__ = None
+
+class boolop(AST):
+	_fields = None
+	__doc__ = None
+	_attributes = []
+class And(boolop):
+	_fields = None
+	__doc__ = None
+
+class stmt(AST):
+	_fields = None
+	__doc__ = None
+	_attributes = ['lineno', 'col_offset']
+class Assert(stmt):
+	_fields = ('test', 'msg')
+	__doc__ = None
+
+class Assign(stmt):
+	_fields = ('targets', 'value')
+	__doc__ = None
+
+class expr(AST):
+	_fields = None
+	__doc__ = None
+	_attributes = ['lineno', 'col_offset']
+class Attribute(expr):
+	_fields = ('value', 'attr', 'ctx')
+	__doc__ = None
+
+class AugAssign(stmt):
+	_fields = ('target', 'op', 'value')
+	__doc__ = None
+
+class expr_context(AST):
+	_fields = None
+	__doc__ = None
+	_attributes = []
+class AugLoad(expr_context):
+	_fields = None
+	__doc__ = None
+
+class AugStore(expr_context):
+	_fields = None
+	__doc__ = None
+
+class BinOp(expr):
+	_fields = ('left', 'op', 'right')
+	__doc__ = None
+
+class BitAnd(operator):
+	_fields = None
+	__doc__ = None
+
+class BitOr(operator):
+	_fields = None
+	__doc__ = None
+
+class BitXor(operator):
+	_fields = None
+	__doc__ = None
+
+class BoolOp(expr):
+	_fields = ('op', 'values')
+	__doc__ = None
+
+class Break(stmt):
+	_fields = None
+	__doc__ = None
+
+class Call(expr):
+	_fields = ('func', 'args', 'keywords', 'starargs', 'kwargs')
+	__doc__ = None
+
+class ClassDef(stmt):
+	_fields = ('name', 'bases', 'body')
+	__doc__ = None
+
+class Compare(expr):
+	_fields = ('left', 'ops', 'comparators')
+	__doc__ = None
+
+class Continue(stmt):
+	_fields = None
+	__doc__ = None
+
+class Del(expr_context):
+	_fields = None
+	__doc__ = None
+
+class Delete(stmt):
+	_fields = ('targets',)
+	__doc__ = None
+
+class Dict(expr):
+	_fields = ('keys', 'values')
+	__doc__ = None
+
+class Div(operator):
+	_fields = None
+	__doc__ = None
+
+class slice(AST):
+	_fields = None
+	__doc__ = None
+	_attributes = []
+class Ellipsis(slice):
+	_fields = None
+	__doc__ = None
+
+class cmpop(AST):
+	_fields = None
+	__doc__ = None
+	_attributes = []
+class Eq(cmpop):
+	_fields = None
+	__doc__ = None
+
+class Exec(stmt):
+	_fields = ('body', 'globals', 'locals')
+	__doc__ = None
+
+class Expr(stmt):
+	_fields = ('value',)
+	__doc__ = None
+
+class mod(AST):
+	_fields = None
+	__doc__ = None
+	_attributes = []
+class Expression(mod):
+	_fields = ('body',)
+	__doc__ = None
+
+class ExtSlice(slice):
+	_fields = ('dims',)
+	__doc__ = None
+
+class FloorDiv(operator):
+	_fields = None
+	__doc__ = None
+
+class For(stmt):
+	_fields = ('target', 'iter', 'body', 'orelse')
+	__doc__ = None
+
+class FunctionDef(stmt):
+	_fields = ('name', 'args', 'body', 'decorators')
+	__doc__ = None
+
+class GeneratorExp(expr):
+	_fields = ('elt', 'generators')
+	__doc__ = None
+
+class Global(stmt):
+	_fields = ('names',)
+	__doc__ = None
+
+class Gt(cmpop):
+	_fields = None
+	__doc__ = None
+
+class GtE(cmpop):
+	_fields = None
+	__doc__ = None
+
+class If(stmt):
+	_fields = ('test', 'body', 'orelse')
+	__doc__ = None
+
+class IfExp(expr):
+	_fields = ('test', 'body', 'orelse')
+	__doc__ = None
+
+class Import(stmt):
+	_fields = ('names',)
+	__doc__ = None
+
+class ImportFrom(stmt):
+	_fields = ('module', 'names', 'level')
+	__doc__ = None
+
+class In(cmpop):
+	_fields = None
+	__doc__ = None
+
+class Index(slice):
+	_fields = ('value',)
+	__doc__ = None
+
+class Interactive(mod):
+	_fields = ('body',)
+	__doc__ = None
+
+class unaryop(AST):
+	_fields = None
+	__doc__ = None
+	_attributes = []
+class Invert(unaryop):
+	_fields = None
+	__doc__ = None
+
+class Is(cmpop):
+	_fields = None
+	__doc__ = None
+
+class IsNot(cmpop):
+	_fields = None
+	__doc__ = None
+
+class LShift(operator):
+	_fields = None
+	__doc__ = None
+
+class Lambda(expr):
+	_fields = ('args', 'body')
+	__doc__ = None
+
+class List(expr):
+	_fields = ('elts', 'ctx')
+	__doc__ = None
+
+class ListComp(expr):
+	_fields = ('elt', 'generators')
+	__doc__ = None
+
+class Load(expr_context):
+	_fields = None
+	__doc__ = None
+
+class Lt(cmpop):
+	_fields = None
+	__doc__ = None
+
+class LtE(cmpop):
+	_fields = None
+	__doc__ = None
+
+class Mod(operator):
+	_fields = None
+	__doc__ = None
+
+class Module(mod):
+	_fields = ('body',)
+	__doc__ = None
+
+class Mult(operator):
+	_fields = None
+	__doc__ = None
+
+class Name(expr):
+	_fields = ('id', 'ctx')
+	__doc__ = None
+
+class Not(unaryop):
+	_fields = None
+	__doc__ = None
+
+class NotEq(cmpop):
+	_fields = None
+	__doc__ = None
+
+class NotIn(cmpop):
+	_fields = None
+	__doc__ = None
+
+class Num(expr):
+	_fields = ('n',)
+	__doc__ = None
+
+class Or(boolop):
+	_fields = None
+	__doc__ = None
+
+class Param(expr_context):
+	_fields = None
+	__doc__ = None
+
+class Pass(stmt):
+	_fields = None
+	__doc__ = None
+
+class Pow(operator):
+	_fields = None
+	__doc__ = None
+
+class Print(stmt):
+	_fields = ('dest', 'values', 'nl')
+	__doc__ = None
+
+class RShift(operator):
+	_fields = None
+	__doc__ = None
+
+class Raise(stmt):
+	_fields = ('type', 'inst', 'tback')
+	__doc__ = None
+
+class Repr(expr):
+	_fields = ('value',)
+	__doc__ = None
+
+class Return(stmt):
+	_fields = ('value',)
+	__doc__ = None
+
+class Slice(slice):
+	_fields = ('lower', 'upper', 'step')
+	__doc__ = None
+
+class Store(expr_context):
+	_fields = None
+	__doc__ = None
+
+class Str(expr):
+	_fields = ('s',)
+	__doc__ = None
+
+class Sub(operator):
+	_fields = None
+	__doc__ = None
+
+class Subscript(expr):
+	_fields = ('value', 'slice', 'ctx')
+	__doc__ = None
+
+class Suite(mod):
+	_fields = ('body',)
+	__doc__ = None
+
+class TryExcept(stmt):
+	_fields = ('body', 'handlers', 'orelse')
+	__doc__ = None
+
+class TryFinally(stmt):
+	_fields = ('body', 'finalbody')
+	__doc__ = None
+
+class Tuple(expr):
+	_fields = ('elts', 'ctx')
+	__doc__ = None
+
+class UAdd(unaryop):
+	_fields = None
+	__doc__ = None
+
+class USub(unaryop):
+	_fields = None
+	__doc__ = None
+
+class UnaryOp(expr):
+	_fields = ('op', 'operand')
+	__doc__ = None
+
+class While(stmt):
+	_fields = ('test', 'body', 'orelse')
+	__doc__ = None
+
+class With(stmt):
+	_fields = ('context_expr', 'optional_vars', 'body')
+	__doc__ = None
+
+class Yield(expr):
+	_fields = ('value',)
+	__doc__ = None
+
+class alias(AST):
+	_fields = ('name', 'asname')
+	__doc__ = None
+
+class arguments(AST):
+	_fields = ('args', 'vararg', 'kwarg', 'defaults')
+	__doc__ = None
+
+class boolop(AST):
+	_fields = None
+	__doc__ = None
+	_attributes = []
+
+class cmpop(AST):
+	_fields = None
+	__doc__ = None
+	_attributes = []
+
+class comprehension(AST):
+	_fields = ('target', 'iter', 'ifs')
+	__doc__ = None
+
+class excepthandler(AST):
+	_fields = ('type', 'name', 'body', 'lineno', 'col_offset')
+	__doc__ = None
+
+class expr(AST):
+	_fields = None
+	__doc__ = None
+	_attributes = ['lineno', 'col_offset']
+
+class expr_context(AST):
+	_fields = None
+	__doc__ = None
+	_attributes = []
+
+class keyword(AST):
+	_fields = ('arg', 'value')
+	__doc__ = None
+
+class mod(AST):
+	_fields = None
+	__doc__ = None
+	_attributes = []
+
+class operator(AST):
+	_fields = None
+	__doc__ = None
+	_attributes = []
+
+class slice(AST):
+	_fields = None
+	__doc__ = None
+	_attributes = []
+
+class stmt(AST):
+	_fields = None
+	__doc__ = None
+	_attributes = ['lineno', 'col_offset']
+
+class unaryop(AST):
+	_fields = None
+	__doc__ = None
+	_attributes = []
+
new file mode 100644
--- /dev/null
+++ b/genshi/template/ast24.py
@@ -0,0 +1,506 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008 Edgewall Software
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://genshi.edgewall.org/wiki/License.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://genshi.edgewall.org/log/.
+
+"""Emulation of the proper abstract syntax tree API for Python 2.4."""
+
+import compiler
+import compiler.ast
+
+from genshi.template import _ast24 as _ast
+
+__all__ = ['_ast', 'parse']
+__docformat__ = 'restructuredtext en'
+
+
+def _new(cls, *args, **kwargs):
+    ret = cls()
+    if ret._fields:
+        for attr, value in zip(ret._fields, args):
+            if attr in kwargs:
+                raise ValueError, 'Field set both in args and kwargs'
+            setattr(ret, attr, value)
+    for attr in kwargs:
+        if (getattr(ret, '_fields', None) and attr in ret._fields) \
+                or (getattr(ret, '_attributes', None) and 
+                        attr in ret._attributes):
+            setattr(ret, attr, kwargs[attr])
+    return ret
+
+
+class ASTUpgrader(object):
+    """Transformer changing structure of Python 2.4 ASTs to
+    Python 2.5 ones.
+
+    Transforms ``compiler.ast`` Abstract Syntax Tree to builtin ``_ast``.
+    It can use fake`` _ast`` classes and this way allow ``_ast`` emulation
+    in Python 2.4.
+    """
+
+    def __init__(self):
+        self.out_flags = None
+        self.lines = [-1]
+
+    def _new(self, *args, **kwargs):
+        return _new(lineno = self.lines[-1], *args, **kwargs)
+
+    def visit(self, node):
+        if node is None:
+            return None
+        if type(node) is tuple:
+            return tuple([self.visit(n) for n in node])
+        lno = getattr(node, 'lineno', None)
+        if lno is not None:
+            self.lines.append(lno)
+        visitor = getattr(self, 'visit_%s' % node.__class__.__name__, None)
+        if visitor is None:
+            raise Exception('Unhandled node type %r' % type(node))
+
+        retval = visitor(node)
+        if lno is not None:
+            self.lines.pop()
+        return retval
+
+    def visit_Module(self, node):
+        body = self.visit(node.node)
+        if node.doc:
+            body = [self._new(_ast.Expr, self._new(_ast.Str, node.doc))] + body
+        return self._new(_ast.Module, body)
+
+    def visit_Expression(self, node):
+        return self._new(_ast.Expression, self.visit(node.node))
+
+    def _extract_args(self, node):
+        tab = node.argnames[:]
+        if node.flags & compiler.ast.CO_VARKEYWORDS:
+            kwarg = tab[-1]
+            tab = tab[:-1]
+        else:
+            kwarg = None
+
+        if node.flags & compiler.ast.CO_VARARGS:
+            vararg = tab[-1]
+            tab = tab[:-1]
+        else:
+            vararg = None
+
+        def _tup(t):
+            if isinstance(t, str):
+                return self._new(_ast.Name, t, _ast.Store())
+            elif isinstance(t, tuple):
+                elts = [_tup(x) for x in t]
+                return self._new(_ast.Tuple, elts, _ast.Store())
+            else:
+                raise NotImplemented
+            
+        args = []
+        for arg in tab:
+            if isinstance(arg, str):
+                args.append(self._new(_ast.Name, arg, _ast.Param()))
+            elif isinstance(arg, tuple):
+                args.append(_tup(arg))
+            else:
+                assert False, node.__class__
+
+        defaults = map(self.visit, node.defaults)
+        return self._new(_ast.arguments, args, vararg, kwarg, defaults)
+
+
+    def visit_Function(self, node):
+        if getattr(node, 'decorators', ()):
+            decorators = [self.visit(d) for d in node.decorators.nodes]
+        else:
+            decorators = []
+
+        args = self._extract_args(node)
+        body = self.visit(node.code)
+        if node.doc:
+            body = [self._new(_ast.Expr, self._new(_ast.Str, node.doc))] + body
+        return self._new(_ast.FunctionDef, node.name, args, body, decorators)
+
+    def visit_Class(self, node):
+        #self.name_types.append(_ast.Load)
+        bases = [self.visit(b) for b in node.bases]
+        #self.name_types.pop()
+        body = self.visit(node.code)
+        if node.doc:
+            body = [self._new(_ast.Expr, self._new(_ast.Str, node.doc))] + body
+        return self._new(_ast.ClassDef, node.name, bases, body)
+
+    def visit_Return(self, node):
+        return self._new(_ast.Return, self.visit(node.value))
+
+    def visit_Assign(self, node):
+        #self.name_types.append(_ast.Store)
+        targets = [self.visit(t) for t in node.nodes]
+        #self.name_types.pop()
+        return self._new(_ast.Assign, targets, self.visit(node.expr))
+
+    aug_operators = {
+        '+=': _ast.Add,
+        '/=': _ast.Div,
+        '//=': _ast.FloorDiv,
+        '<<=': _ast.LShift,
+        '%=': _ast.Mod,
+        '*=': _ast.Mult,
+        '**=': _ast.Pow,
+        '>>=': _ast.RShift,
+        '-=': _ast.Sub,
+    }
+
+    def visit_AugAssign(self, node):
+        target = self.visit(node.node)
+
+        # Because it's AugAssign target can't be list nor tuple
+        # so we only have to change context of one node
+        target.ctx = _ast.Store()
+        op = self.aug_operators[node.op]()
+        return self._new(_ast.AugAssign, target, op, self.visit(node.expr))
+
+    def _visit_Print(nl):
+        def _visit(self, node):
+            values = [self.visit(v) for v in node.nodes]
+            return self._new(_ast.Print, self.visit(node.dest), values, nl)
+        return _visit
+
+    visit_Print = _visit_Print(False)
+    visit_Printnl = _visit_Print(True)
+    del _visit_Print
+
+    def visit_For(self, node):
+        return self._new(_ast.For, self.visit(node.assign), self.visit(node.list),
+                        self.visit(node.body), self.visit(node.else_))
+
+    def visit_While(self, node):
+        return self._new(_ast.While, self.visit(node.test), self.visit(node.body),
+                        self.visit(node.else_))
+
+    def visit_If(self, node):
+        def _level(tests, else_):
+            test = self.visit(tests[0][0])
+            body = self.visit(tests[0][1])
+            if len(tests) == 1:
+                orelse = self.visit(else_)
+            else:
+                orelse = [_level(tests[1:], else_)]
+            return self._new(_ast.If, test, body, orelse)
+        return _level(node.tests, node.else_)
+
+    def visit_With(self, node):
+        return self._new(_ast.With, self.visit(node.expr),
+                            self.visit(node.vars), self.visit(node.body))
+
+    def visit_Raise(self, node):
+        return self._new(_ast.Raise, self.visit(node.expr1),
+                        self.visit(node.expr2), self.visit(node.expr3))
+
+    def visit_TryExcept(self, node):
+        handlers = []
+        for type, name, body in node.handlers:
+            handlers.append(self._new(_ast.excepthandler, self.visit(type), 
+                            self.visit(name), self.visit(body)))
+        return self._new(_ast.TryExcept, self.visit(node.body),
+                        handlers, self.visit(node.else_))
+
+    def visit_TryFinally(self, node):
+        return self._new(_ast.TryFinally, self.visit(node.body),
+                        self.visit(node.final))
+
+    def visit_Assert(self, node):
+        return self._new(_ast.Assert, self.visit(node.test), self.visit(node.fail))
+
+    def visit_Import(self, node):
+        names = [self._new(_ast.alias, n[0], n[1]) for n in node.names]
+        return self._new(_ast.Import, names)
+
+    def visit_From(self, node):
+        names = [self._new(_ast.alias, n[0], n[1]) for n in node.names]
+        return self._new(_ast.ImportFrom, node.modname, names, 0)
+
+    def visit_Exec(self, node):
+        return self._new(_ast.Exec, self.visit(node.expr),
+                        self.visit(node.locals), self.visit(node.globals))
+
+    def visit_Global(self, node):
+        return self._new(_ast.Global, node.names[:])
+
+    def visit_Discard(self, node):
+        return self._new(_ast.Expr, self.visit(node.expr))
+
+    def _map_class(to):
+        def _visit(self, node):
+            return self._new(to)
+        return _visit
+
+    visit_Pass = _map_class(_ast.Pass)
+    visit_Break = _map_class(_ast.Break)
+    visit_Continue = _map_class(_ast.Continue)
+
+    def _visit_BinOperator(opcls):
+        def _visit(self, node):
+            return self._new(_ast.BinOp, self.visit(node.left), 
+                            opcls(), self.visit(node.right)) 
+        return _visit
+    visit_Add = _visit_BinOperator(_ast.Add)
+    visit_Div = _visit_BinOperator(_ast.Div)
+    visit_FloorDiv = _visit_BinOperator(_ast.FloorDiv)
+    visit_LeftShift = _visit_BinOperator(_ast.LShift)
+    visit_Mod = _visit_BinOperator(_ast.Mod)
+    visit_Mul = _visit_BinOperator(_ast.Mult)
+    visit_Power = _visit_BinOperator(_ast.Pow)
+    visit_RightShift = _visit_BinOperator(_ast.RShift)
+    visit_Sub = _visit_BinOperator(_ast.Sub)
+    del _visit_BinOperator
+
+    def _visit_BitOperator(opcls):
+        def _visit(self, node):
+            def _make(nodes):
+                if len(nodes) == 1:
+                    return self.visit(nodes[0])
+                left = _make(nodes[:-1])
+                right = self.visit(nodes[-1])
+                return self._new(_ast.BinOp, left, opcls(), right)
+            return _make(node.nodes)
+        return _visit
+    visit_Bitand = _visit_BitOperator(_ast.BitAnd)
+    visit_Bitor = _visit_BitOperator(_ast.BitOr)
+    visit_Bitxor = _visit_BitOperator(_ast.BitXor)
+    del _visit_BitOperator
+
+    def _visit_UnaryOperator(opcls):
+        def _visit(self, node):
+            return self._new(_ast.UnaryOp, opcls(), self.visit(node.expr))
+        return _visit
+
+    visit_Invert = _visit_UnaryOperator(_ast.Invert)
+    visit_Not = _visit_UnaryOperator(_ast.Not)
+    visit_UnaryAdd = _visit_UnaryOperator(_ast.UAdd)
+    visit_UnarySub = _visit_UnaryOperator(_ast.USub)
+    del _visit_UnaryOperator
+
+    def _visit_BoolOperator(opcls):
+        def _visit(self, node):
+            values = [self.visit(n) for n in node.nodes]
+            return self._new(_ast.BoolOp, opcls(), values)
+        return _visit
+    visit_And = _visit_BoolOperator(_ast.And)
+    visit_Or = _visit_BoolOperator(_ast.Or)
+    del _visit_BoolOperator
+
+    cmp_operators = {
+        '==': _ast.Eq,
+        '!=': _ast.NotEq,
+        '<': _ast.Lt,
+        '<=': _ast.LtE,
+        '>': _ast.Gt,
+        '>=': _ast.GtE,
+        'is': _ast.Is,
+        'is not': _ast.IsNot,
+        'in': _ast.In,
+        'not in': _ast.NotIn,
+    }
+
+    def visit_Compare(self, node):
+        left = self.visit(node.expr)
+        ops = []
+        comparators = []
+        for optype, expr in node.ops:
+            ops.append(self.cmp_operators[optype]())
+            comparators.append(self.visit(expr))
+        return self._new(_ast.Compare, left, ops, comparators)
+
+    def visit_Lambda(self, node):
+        args = self._extract_args(node)
+        body = self.visit(node.code)
+        return self._new(_ast.Lambda, args, body)
+
+    def visit_IfExp(self, node):
+        return self._new(_ast.IfExp, self.visit(node.test), self.visit(node.then),
+                        self.visit(node.else_))
+
+    def visit_Dict(self, node):
+        keys = [self.visit(x[0]) for x in node.items]
+        values = [self.visit(x[1]) for x in node.items]
+        return self._new(_ast.Dict, keys, values)
+
+    def visit_ListComp(self, node):
+        generators = [self.visit(q) for q in node.quals]
+        return self._new(_ast.ListComp, self.visit(node.expr), generators)
+
+    def visit_GenExprInner(self, node):
+        generators = [self.visit(q) for q in node.quals]
+        return self._new(_ast.GeneratorExp, self.visit(node.expr), generators)
+
+    def visit_GenExpr(self, node):
+        return self.visit(node.code)
+
+    def visit_GenExprFor(self, node):
+        ifs = [self.visit(i) for i in node.ifs]
+        return self._new(_ast.comprehension, self.visit(node.assign),
+                        self.visit(node.iter), ifs)
+
+    def visit_ListCompFor(self, node):
+        ifs = [self.visit(i) for i in node.ifs]
+        return self._new(_ast.comprehension, self.visit(node.assign),
+                        self.visit(node.list), ifs)
+
+    def visit_GenExprIf(self, node):
+        return self.visit(node.test)
+    visit_ListCompIf = visit_GenExprIf
+
+    def visit_Yield(self, node):
+        return self._new(_ast.Yield, self.visit(node.value))
+
+    def visit_CallFunc(self, node):
+        args = []
+        keywords = []
+        for arg in node.args:
+            if isinstance(arg, compiler.ast.Keyword):
+                keywords.append(self._new(_ast.keyword, arg.name, 
+                                        self.visit(arg.expr)))
+            else:
+                args.append(self.visit(arg))
+        return self._new(_ast.Call, self.visit(node.node), args, keywords,
+                    self.visit(node.star_args), self.visit(node.dstar_args))
+
+    def visit_Backquote(self, node):
+        return self._new(_ast.Repr, self.visit(node.expr))
+
+    def visit_Const(self, node):
+        if node.value is None: # appears in slices
+            return None
+        elif isinstance(node.value, (str, unicode,)):
+            return self._new(_ast.Str, node.value)
+        else:
+            return self._new(_ast.Num, node.value)
+
+    def visit_Name(self, node):
+        return self._new(_ast.Name, node.name, _ast.Load())
+
+    def visit_Getattr(self, node):
+        return self._new(_ast.Attribute, self.visit(node.expr), node.attrname,
+                         _ast.Load())
+
+    def visit_Tuple(self, node):
+        nodes = [self.visit(n) for n in node.nodes]
+        return self._new(_ast.Tuple, nodes, _ast.Load())
+
+    def visit_List(self, node):
+        nodes = [self.visit(n) for n in node.nodes]
+        return self._new(_ast.List, nodes, _ast.Load())
+
+    def get_ctx(self, flags):
+        if flags == 'OP_DELETE':
+            return _ast.Del()
+        elif flags == 'OP_APPLY':
+            return _ast.Load()
+        elif flags == 'OP_ASSIGN':
+            return _ast.Store()
+        else:
+            # FIXME Exception here
+            assert False, repr(flags)
+
+    def visit_AssName(self, node):
+        self.out_flags = node.flags
+        ctx = self.get_ctx(node.flags)
+        return self._new(_ast.Name, node.name, ctx)
+
+    def visit_AssAttr(self, node):
+        self.out_flags = node.flags
+        ctx = self.get_ctx(node.flags)
+        return self._new(_ast.Attribute, self.visit(node.expr), 
+                         node.attrname, ctx)
+
+    def _visit_AssCollection(cls):
+        def _visit(self, node):
+            flags = None
+            elts = []
+            for n in node.nodes:
+                elts.append(self.visit(n))
+                if flags is None:
+                    flags = self.out_flags
+                else:
+                    assert flags == self.out_flags
+            self.out_flags = flags
+            ctx = self.get_ctx(flags)
+            return self._new(cls, elts, ctx)
+        return _visit
+
+    visit_AssList = _visit_AssCollection(_ast.List)
+    visit_AssTuple = _visit_AssCollection(_ast.Tuple)
+    del _visit_AssCollection
+
+    def visit_Slice(self, node):
+        lower = self.visit(node.lower)
+        upper = self.visit(node.upper)
+        ctx = self.get_ctx(node.flags)
+        self.out_flags = node.flags
+        return self._new(_ast.Subscript, self.visit(node.expr),
+                    self._new(_ast.Slice, lower, upper, None), ctx)
+
+    def visit_Subscript(self, node):
+        ctx = self.get_ctx(node.flags)
+        subs = [self.visit(s) for s in node.subs]
+
+        advanced = (_ast.Slice, _ast.Ellipsis)
+        slices = []
+        nonindex = False
+        for sub in subs:
+            if isinstance(sub, advanced):
+                nonindex = True
+                slices.append(sub)
+            else:
+                slices.append(self._new(_ast.Index, sub))
+        if len(slices) == 1:
+            slice = slices[0]
+        elif nonindex:
+            slice = self._new(_ast.ExtSlice, slices)
+        else:
+            slice = self._new(_ast.Tuple, slices, _ast.Load())
+
+        self.out_flags = node.flags
+        return self._new(_ast.Subscript, self.visit(node.expr), slice, ctx)
+
+    def visit_Sliceobj(self, node):
+        a = node.nodes + [None]*(3 - len(node.nodes))
+        a = map(self.visit, a)
+        return self._new(_ast.Slice, a[0], a[1], a[2])
+
+    def visit_Ellipsis(self, node):
+        return self._new(_ast.Ellipsis)
+
+    def visit_Stmt(self, node):
+        def _check_del(n):
+            # del x is just AssName('x', 'OP_DELETE')
+            # we want to transform it to Delete([Name('x', Del())])
+            dcls = (_ast.Name, _ast.List, _ast.Subscript, _ast.Attribute)
+            if isinstance(n, dcls) and isinstance(n.ctx, _ast.Del):
+                return self._new(_ast.Delete, [n])
+            elif isinstance(n, _ast.Tuple) and isinstance(n.ctx, _ast.Del):
+                # unpack last tuple to avoid making del (x, y, z,);
+                # out of del x, y, z; (there's no difference between
+                # this two in compiler.ast)
+                return self._new(_ast.Delete, n.elts)
+            else:
+                return n
+        def _keep(n):
+            if isinstance(n, _ast.Expr) and n.value is None:
+                return False
+            else:
+                return True
+        statements = [_check_del(self.visit(n)) for n in node.nodes]
+        return filter(_keep, statements)
+
+
+def parse(source, mode):
+    node = compiler.parse(source, mode)
+    return ASTUpgrader().visit(node)
new file mode 100644
--- /dev/null
+++ b/genshi/template/astgae.py
@@ -0,0 +1,104 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008 Edgewall Software
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://genshi.edgewall.org/wiki/License.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://genshi.edgewall.org/log/.
+
+"""Support for using the Python AST on Google App Engine."""
+
+__all__ = ['restore']
+__docformat__ = 'restructuredtext en'
+
+
+def restore(_ast):
+    """Gross hack to restore the required classes to the _ast module if it
+    appears to be missing them. Mostly lifted from Mako.
+    """
+    _ast.PyCF_ONLY_AST = 2 << 9
+
+    e = compile('True', '<string>', 'eval', _ast.PyCF_ONLY_AST)
+    _ast.Expression = type(e)
+    for cls in _ast.Expression.__mro__:
+        if cls.__name__ == 'AST':
+            _ast.AST = cls
+
+    m = compile("""\
+foo()
+bar = 'fish'
+baz += bar
+1 + 2 - 3 * 4 / 5 ** 6
+6 // 7 % 8 << 9 >> 10
+11 & 12 ^ 13 | 14
+15 and 16 or 17
+-baz + (not +18) - ~17
+baz and 'foo' or 'bar'
+(fish is baz == baz) is not baz != fish
+fish > baz < fish >= baz <= fish
+fish in baz not in (1, 2, 3)
+baz[1, 1:2, ...]
+""", '<string>', 'exec', _ast.PyCF_ONLY_AST)
+
+    _ast.Module = type(m)
+
+    _ast.Expr = type(m.body[0])
+    _ast.Call = type(m.body[0].value)
+
+    _ast.Assign = type(m.body[1])
+    _ast.Name = type(m.body[1].targets[0])
+    _ast.Store = type(m.body[1].targets[0].ctx)
+    _ast.Str = type(m.body[1].value)
+
+    _ast.AugAssign = type(m.body[2])
+    _ast.Load = type(m.body[2].value.ctx)
+
+    _ast.Sub = type(m.body[3].value.op)
+    _ast.Add = type(m.body[3].value.left.op)
+    _ast.Div = type(m.body[3].value.right.op)
+    _ast.Mult = type(m.body[3].value.right.left.op)
+    _ast.Pow = type(m.body[3].value.right.right.op)
+
+    _ast.RShift = type(m.body[4].value.op)
+    _ast.LShift = type(m.body[4].value.left.op)
+    _ast.Mod = type(m.body[4].value.left.left.op)
+    _ast.FloorDiv = type(m.body[4].value.left.left.left.op)
+
+    _ast.BitOr = type(m.body[5].value.op)
+    _ast.BitXor = type(m.body[5].value.left.op)
+    _ast.BitAnd = type(m.body[5].value.left.left.op)
+
+    _ast.Or = type(m.body[6].value.op)
+    _ast.And = type(m.body[6].value.values[0].op)
+
+    _ast.Invert = type(m.body[7].value.right.op)
+    _ast.Not = type(m.body[7].value.left.right.op)
+    _ast.UAdd = type(m.body[7].value.left.right.operand.op)
+    _ast.USub = type(m.body[7].value.left.left.op)
+
+    _ast.Or = type(m.body[8].value.op)
+    _ast.And = type(m.body[8].value.values[0].op)
+
+    _ast.IsNot = type(m.body[9].value.ops[0])
+    _ast.NotEq = type(m.body[9].value.ops[1])
+    _ast.Is = type(m.body[9].value.left.ops[0])
+    _ast.Eq = type(m.body[9].value.left.ops[1])
+
+    _ast.Gt = type(m.body[10].value.ops[0])
+    _ast.Lt = type(m.body[10].value.ops[1])
+    _ast.GtE = type(m.body[10].value.ops[2])
+    _ast.LtE = type(m.body[10].value.ops[3])
+
+    _ast.In = type(m.body[11].value.ops[0])
+    _ast.NotIn = type(m.body[11].value.ops[1])
+    _ast.Tuple = type(m.body[11].value.comparators[1])
+
+    _ast.ExtSlice = type(m.body[12].value.slice)
+    _ast.Index = type(m.body[12].value.slice.dims[0])
+    _ast.Slice = type(m.body[12].value.slice.dims[1])
+    _ast.Ellipsis = type(m.body[12].value.slice.dims[2])
new file mode 100644
--- /dev/null
+++ b/genshi/template/astutil.py
@@ -0,0 +1,778 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008 Edgewall Software
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://genshi.edgewall.org/wiki/License.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://genshi.edgewall.org/log/.
+
+"""Support classes for generating code from abstract syntax trees."""
+
+try:
+    import _ast
+except ImportError:
+    from genshi.template.ast24 import _ast, parse
+else:
+    if not hasattr(_ast, 'AST'):
+        from genshi.template.astgae import restore
+        restore(_ast)
+    def parse(source, mode):
+        return compile(source, '', mode, _ast.PyCF_ONLY_AST)
+
+
+__docformat__ = 'restructuredtext en'
+
+
+class ASTCodeGenerator(object):
+    """General purpose base class for AST transformations.
+
+    Every visitor method can be overridden to return an AST node that has been
+    altered or replaced in some way.
+    """
+    def __init__(self, tree):
+        self.lines_info = []
+        self.line_info = None
+        self.code = ''
+        self.line = None
+        self.last = None
+        self.indent = 0
+        self.blame_stack = []
+        self.visit(tree)
+        if self.line.strip():
+            self.code += self.line + '\n'
+            self.lines_info.append(self.line_info)
+        self.line = None
+        self.line_info = None
+
+    def _change_indent(self, delta):
+        self.indent += delta
+
+    def _new_line(self):
+        if self.line is not None:
+            self.code += self.line + '\n'
+            self.lines_info.append(self.line_info)
+        self.line = ' '*4*self.indent
+        if len(self.blame_stack) == 0:
+            self.line_info = []
+            self.last = None
+        else:
+            self.line_info = [(0, self.blame_stack[-1],)]
+            self.last = self.blame_stack[-1]
+
+    def _write(self, s):
+        if len(s) == 0:
+            return
+        if len(self.blame_stack) == 0:
+            if self.last is not None:
+                self.last = None
+                self.line_info.append((len(self.line), self.last))
+        else:
+            if self.last != self.blame_stack[-1]:
+                self.last = self.blame_stack[-1]
+                self.line_info.append((len(self.line), self.last))
+        self.line += s
+
+    def visit(self, node):
+        if node is None:
+            return None
+        if type(node) is tuple:
+            return tuple([self.visit(n) for n in node])
+        try:
+            self.blame_stack.append((node.lineno, node.col_offset,))
+            info = True
+        except AttributeError:
+            info = False
+        visitor = getattr(self, 'visit_%s' % node.__class__.__name__, None)
+        if visitor is None:
+            raise Exception('Unhandled node type %r' % type(node))
+        ret = visitor(node)
+        if info:
+            self.blame_stack.pop()
+        return ret
+
+    def visit_Module(self, node):
+        for n in node.body:
+            self.visit(n)
+    visit_Interactive = visit_Module
+    visit_Suite = visit_Module
+
+    def visit_Expression(self, node):
+        self._new_line()
+        return self.visit(node.body)
+
+    # arguments = (expr* args, identifier? vararg,
+    #                 identifier? kwarg, expr* defaults)
+    def visit_arguments(self, node):
+        first = True
+        for i, arg in enumerate(node.args):
+            if not first:
+                self._write(', ')
+            else:
+                first = False
+            self.visit(arg)
+            if i < len(node.defaults):
+                self._write('=')
+                self.visit(node.defaults[i])
+        if getattr(node, 'vararg', None):
+            if not first:
+                self._write(', ')
+            else:
+                first = False
+            self._write('*' + node.vararg)
+        if getattr(node, 'kwarg', None):
+            if not first:
+                self._write(', ')
+            else:
+                first = False
+            self._write('**' + node.kwarg)
+
+    # FunctionDef(identifier name, arguments args,
+    #                           stmt* body, expr* decorators)
+    def visit_FunctionDef(self, node):
+        for decorator in getattr(node, 'decorators', ()):
+            self._new_line()
+            self._write('@')
+            self.visit(decorator)
+        self._new_line()
+        self._write('def ' + node.name + '(')
+        self.visit(node.args)
+        self._write('):')
+        self._change_indent(1)
+        for statement in node.body:
+            self.visit(statement)
+        self._change_indent(-1)
+
+    # ClassDef(identifier name, expr* bases, stmt* body)
+    def visit_ClassDef(self, node):
+        self._new_line()
+        self._write('class ' + node.name)
+        if node.bases:
+            self._write('(')
+            self.visit(node.bases[0])
+            for base in node.bases[1:]:
+                self._write(', ')
+                self.visit(base)
+            self._write(')')
+        self._write(':')
+        self._change_indent(1)
+        for statement in node.body:
+            self.visit(statement)
+        self._change_indent(-1)
+
+    # Return(expr? value)
+    def visit_Return(self, node):
+        self._new_line()
+        self._write('return')
+        if getattr(node, 'value', None):
+            self._write(' ')
+            self.visit(node.value)
+
+    # Delete(expr* targets)
+    def visit_Delete(self, node):
+        self._new_line()
+        self._write('del ')
+        self.visit(node.targets[0])
+        for target in node.targets[1:]:
+            self._write(', ')
+            self.visit(target)
+
+    # Assign(expr* targets, expr value)
+    def visit_Assign(self, node):
+        self._new_line()
+        for target in node.targets:
+            self.visit(target)
+            self._write(' = ')
+        self.visit(node.value)
+
+    # AugAssign(expr target, operator op, expr value)
+    def visit_AugAssign(self, node):
+        self._new_line()
+        self.visit(node.target)
+        self._write(' ' + self.binary_operators[node.op.__class__] + '= ')
+        self.visit(node.value)
+
+    # Print(expr? dest, expr* values, bool nl)
+    def visit_Print(self, node):
+        self._new_line()
+        self._write('print')
+        if getattr(node, 'dest', None):
+            self._write(' >> ')
+            self.visit(node.dest)
+            if getattr(node, 'values', None):
+                self._write(', ')
+        if getattr(node, 'values', None):
+            self.visit(node.values[0])
+            for value in node.values[1:]:
+                self._write(', ')
+                self.visit(value)
+        if not node.nl:
+            self._write(',')
+
+    # For(expr target, expr iter, stmt* body, stmt* orelse)
+    def visit_For(self, node):
+        self._new_line()
+        self._write('for ')
+        self.visit(node.target)
+        self._write(' in ')
+        self.visit(node.iter)
+        self._write(':')
+        self._change_indent(1)
+        for statement in node.body:
+            self.visit(statement)
+        self._change_indent(-1)
+        if getattr(node, 'orelse', None):
+            self._new_line()
+            self._write('else:')
+            self._change_indent(1)
+            for statement in node.orelse:
+                self.visit(statement)
+            self._change_indent(-1)
+
+    # While(expr test, stmt* body, stmt* orelse)
+    def visit_While(self, node):
+        self._new_line()
+        self._write('while ')
+        self.visit(node.test)
+        self._write(':')
+        self._change_indent(1)
+        for statement in node.body:
+            self.visit(statement)
+        self._change_indent(-1)
+        if getattr(node, 'orelse', None):
+            self._new_line()
+            self._write('else:')
+            self._change_indent(1)
+            for statement in node.orelse:
+                self.visit(statement)
+            self._change_indent(-1)
+
+    # If(expr test, stmt* body, stmt* orelse)
+    def visit_If(self, node):
+        self._new_line()
+        self._write('if ')
+        self.visit(node.test)
+        self._write(':')
+        self._change_indent(1)
+        for statement in node.body:
+            self.visit(statement)
+        self._change_indent(-1)
+        if getattr(node, 'orelse', None):
+            self._new_line()
+            self._write('else:')
+            self._change_indent(1)
+            for statement in node.orelse:
+                self.visit(statement)
+            self._change_indent(-1)
+
+    # With(expr context_expr, expr? optional_vars, stmt* body)
+    def visit_With(self, node):
+        self._new_line()
+        self._write('with ')
+        self.visit(node.context_expr)
+        if getattr(node, 'optional_vars', None):
+            self._write(' as ')
+            self.visit(node.optional_vars)
+        self._write(':')
+        self._change_indent(1)
+        for statement in node.body:
+            self.visit(statement)
+        self._change_indent(-1)
+
+
+    # Raise(expr? type, expr? inst, expr? tback)
+    def visit_Raise(self, node):
+        self._new_line()
+        self._write('raise')
+        if not node.type:
+            return
+        self._write(' ')
+        self.visit(node.type)
+        if not node.inst:
+            return
+        self._write(', ')
+        self.visit(node.inst)
+        if not node.tback:
+            return
+        self._write(', ')
+        self.visit(node.tback)
+
+    # TryExcept(stmt* body, excepthandler* handlers, stmt* orelse)
+    def visit_TryExcept(self, node):
+        self._new_line()
+        self._write('try:')
+        self._change_indent(1)
+        for statement in node.body:
+            self.visit(statement)
+        self._change_indent(-1)
+        if getattr(node, 'handlers', None):
+            for handler in node.handlers:
+                self.visit(handler)
+        self._new_line()
+        if getattr(node, 'orelse', None):
+            self._write('else:')
+            self._change_indent(1)
+            for statement in node.orelse:
+                self.visit(statement)
+            self._change_indent(-1)
+
+    # excepthandler = (expr? type, expr? name, stmt* body)
+    def visit_ExceptHandler(self, node):
+        self._new_line()
+        self._write('except')
+        if getattr(node, 'type', None):
+            self._write(' ')
+            self.visit(node.type)
+        if getattr(node, 'name', None):
+            self._write(', ')
+            self.visit(node.name)
+        self._write(':')
+        self._change_indent(1)
+        for statement in node.body:
+            self.visit(statement)
+        self._change_indent(-1)
+    visit_excepthandler = visit_ExceptHandler
+
+    # TryFinally(stmt* body, stmt* finalbody)
+    def visit_TryFinally(self, node):
+        self._new_line()
+        self._write('try:')
+        self._change_indent(1)
+        for statement in node.body:
+            self.visit(statement)
+        self._change_indent(-1)
+
+        if getattr(node, 'finalbody', None):
+            self._new_line()
+            self._write('finally:')
+            self._change_indent(1)
+            for statement in node.finalbody:
+                self.visit(statement)
+            self._change_indent(-1)
+
+    # Assert(expr test, expr? msg)
+    def visit_Assert(self, node):
+        self._new_line()
+        self._write('assert ')
+        self.visit(node.test)
+        if getattr(node, 'msg', None):
+            self._write(', ')
+            self.visit(node.msg)
+
+    def visit_alias(self, node):
+        self._write(node.name)
+        if getattr(node, 'asname', None):
+            self._write(' as ')
+            self._write(node.asname)
+
+    # Import(alias* names)
+    def visit_Import(self, node):
+        self._new_line()
+        self._write('import ')
+        self.visit(node.names[0])
+        for name in node.names[1:]:
+            self._write(', ')
+            self.visit(name)
+
+    # ImportFrom(identifier module, alias* names, int? level)
+    def visit_ImportFrom(self, node):
+        self._new_line()
+        self._write('from ')
+        if node.level:
+            self._write('.' * node.level)
+        self._write(node.module)
+        self._write(' import ')
+        self.visit(node.names[0])
+        for name in node.names[1:]:
+            self._write(', ')
+            self.visit(name)
+
+    # Exec(expr body, expr? globals, expr? locals)
+    def visit_Exec(self, node):
+        self._new_line()
+        self._write('exec ')
+        self.visit(node.body)
+        if not node.globals:
+            return
+        self._write(', ')
+        self.visit(node.globals)
+        if not node.locals:
+            return
+        self._write(', ')
+        self.visit(node.locals)
+
+    # Global(identifier* names)
+    def visit_Global(self, node):
+        self._new_line()
+        self._write('global ')
+        self.visit(node.names[0])
+        for name in node.names[1:]:
+            self._write(', ')
+            self.visit(name)
+
+    # Expr(expr value)
+    def visit_Expr(self, node):
+        self._new_line()
+        self.visit(node.value)
+
+    # Pass
+    def visit_Pass(self, node):
+        self._new_line()
+        self._write('pass')
+
+    # Break
+    def visit_Break(self, node):
+        self._new_line()
+        self._write('break')
+
+    # Continue
+    def visit_Continue(self, node):
+        self._new_line()
+        self._write('continue')
+
+    ### EXPRESSIONS
+    def with_parens(f):
+        def _f(self, node):
+            self._write('(')
+            f(self, node)
+            self._write(')')
+        return _f
+
+    bool_operators = {_ast.And: 'and', _ast.Or: 'or'}
+
+    # BoolOp(boolop op, expr* values)
+    @with_parens
+    def visit_BoolOp(self, node):
+        joiner = ' ' + self.bool_operators[node.op.__class__] + ' '
+        self.visit(node.values[0])
+        for value in node.values[1:]:
+            self._write(joiner)
+            self.visit(value)
+
+    binary_operators = {
+        _ast.Add: '+',
+        _ast.Sub: '-',
+        _ast.Mult: '*',
+        _ast.Div: '/',
+        _ast.Mod: '%',
+        _ast.Pow: '**',
+        _ast.LShift: '<<',
+        _ast.RShift: '>>',
+        _ast.BitOr: '|',
+        _ast.BitXor: '^',
+        _ast.BitAnd: '&',
+        _ast.FloorDiv: '//'
+    }
+
+    # BinOp(expr left, operator op, expr right)
+    @with_parens
+    def visit_BinOp(self, node):
+        self.visit(node.left)
+        self._write(' ' + self.binary_operators[node.op.__class__] + ' ')
+        self.visit(node.right)
+
+    unary_operators = {
+        _ast.Invert: '~',
+        _ast.Not: 'not',
+        _ast.UAdd: '+',
+        _ast.USub: '-',
+    }
+
+    # UnaryOp(unaryop op, expr operand)
+    def visit_UnaryOp(self, node):
+        self._write(self.unary_operators[node.op.__class__] + ' ')
+        self.visit(node.operand)
+
+    # Lambda(arguments args, expr body)
+    @with_parens
+    def visit_Lambda(self, node):
+        self._write('lambda ')
+        self.visit(node.args)
+        self._write(': ')
+        self.visit(node.body)
+
+    # IfExp(expr test, expr body, expr orelse)
+    @with_parens
+    def visit_IfExp(self, node):
+        self.visit(node.body)
+        self._write(' if ')
+        self.visit(node.test)
+        self._write(' else ')
+        self.visit(node.orelse)
+
+    # Dict(expr* keys, expr* values)
+    def visit_Dict(self, node):
+        self._write('{')
+        for key, value in zip(node.keys, node.values):
+            self.visit(key)
+            self._write(': ')
+            self.visit(value)
+            self._write(', ')
+        self._write('}')
+
+    # ListComp(expr elt, comprehension* generators)
+    def visit_ListComp(self, node):
+        self._write('[')
+        self.visit(node.elt)
+        for generator in node.generators:
+            # comprehension = (expr target, expr iter, expr* ifs)
+            self._write(' for ')
+            self.visit(generator.target)
+            self._write(' in ')
+            self.visit(generator.iter)
+            for ifexpr in generator.ifs:
+                self._write(' if ')
+                self.visit(ifexpr)
+        self._write(']')
+
+    # GeneratorExp(expr elt, comprehension* generators)
+    def visit_GeneratorExp(self, node):
+        self._write('(')
+        self.visit(node.elt)
+        for generator in node.generators:
+            # comprehension = (expr target, expr iter, expr* ifs)
+            self._write(' for ')
+            self.visit(generator.target)
+            self._write(' in ')
+            self.visit(generator.iter)
+            for ifexpr in generator.ifs:
+                self._write(' if ')
+                self.visit(ifexpr)
+        self._write(')')
+
+    # Yield(expr? value)
+    def visit_Yield(self, node):
+        self._write('yield')
+        if getattr(node, 'value', None):
+            self._write(' ')
+            self.visit(node.value)
+
+    comparision_operators = {
+        _ast.Eq: '==',
+        _ast.NotEq: '!=',
+        _ast.Lt: '<',
+        _ast.LtE: '<=',
+        _ast.Gt: '>',
+        _ast.GtE: '>=',
+        _ast.Is: 'is',
+        _ast.IsNot: 'is not',
+        _ast.In: 'in',
+        _ast.NotIn: 'not in',
+    }
+
+    # Compare(expr left, cmpop* ops, expr* comparators)
+    @with_parens
+    def visit_Compare(self, node):
+        self.visit(node.left)
+        for op, comparator in zip(node.ops, node.comparators):
+            self._write(' ' + self.comparision_operators[op.__class__] + ' ')
+            self.visit(comparator)
+
+    # Call(expr func, expr* args, keyword* keywords,
+    #                         expr? starargs, expr? kwargs)
+    def visit_Call(self, node):
+        self.visit(node.func)
+        self._write('(')
+        first = True
+        for arg in node.args:
+            if not first:
+                self._write(', ')
+            first = False
+            self.visit(arg)
+
+        for keyword in node.keywords:
+            if not first:
+                self._write(', ')
+            first = False
+            # keyword = (identifier arg, expr value)
+            self._write(keyword.arg)
+            self._write('=')
+            self.visit(keyword.value)
+        if getattr(node, 'starargs', None):
+            if not first:
+                self._write(', ')
+            first = False
+            self._write('*')
+            self.visit(node.starargs)
+
+        if getattr(node, 'kwargs', None):
+            if not first:
+                self._write(', ')
+            first = False
+            self._write('**')
+            self.visit(node.kwargs)
+        self._write(')')
+
+    # Repr(expr value)
+    def visit_Repr(self, node):
+        self._write('`')
+        self.visit(node.value)
+        self._write('`')
+
+    # Num(object n)
+    def visit_Num(self, node):
+        self._write(repr(node.n))
+
+    # Str(string s)
+    def visit_Str(self, node):
+        self._write(repr(node.s))
+
+    # Attribute(expr value, identifier attr, expr_context ctx)
+    def visit_Attribute(self, node):
+        self.visit(node.value)
+        self._write('.')
+        self._write(node.attr)
+
+    # Subscript(expr value, slice slice, expr_context ctx)
+    def visit_Subscript(self, node):
+        self.visit(node.value)
+        self._write('[')
+        def _process_slice(node):
+            if isinstance(node, _ast.Ellipsis):
+                self._write('...')
+            elif isinstance(node, _ast.Slice):
+                if getattr(node, 'lower', 'None'):
+                    self.visit(node.lower)
+                self._write(':')
+                if getattr(node, 'upper', None):
+                    self.visit(node.upper)
+                if getattr(node, 'step', None):
+                    self._write(':')
+                    self.visit(node.step)
+            elif isinstance(node, _ast.Index):
+                self.visit(node.value)
+            elif isinstance(node, _ast.ExtSlice):
+                self.visit(node.dims[0])
+                for dim in node.dims[1:]:
+                    self._write(', ')
+                    self.visit(dim)
+            else:
+                raise NotImplemented, 'Slice type not implemented'
+        _process_slice(node.slice)
+        self._write(']')
+
+    # Name(identifier id, expr_context ctx)
+    def visit_Name(self, node):
+        self._write(node.id)
+
+    # List(expr* elts, expr_context ctx)
+    def visit_List(self, node):
+        self._write('[')
+        for elt in node.elts:
+            self.visit(elt)
+            self._write(', ')
+        self._write(']')
+
+    # Tuple(expr *elts, expr_context ctx)
+    def visit_Tuple(self, node):
+        self._write('(')
+        for elt in node.elts:
+            self.visit(elt)
+            self._write(', ')
+        self._write(')')
+
+
+class ASTTransformer(object):
+    """General purpose base class for AST transformations.
+    
+    Every visitor method can be overridden to return an AST node that has been
+    altered or replaced in some way.
+    """
+
+    def visit(self, node):
+        if node is None:
+            return None
+        if type(node) is tuple:
+            return tuple([self.visit(n) for n in node])
+        visitor = getattr(self, 'visit_%s' % node.__class__.__name__, None)
+        if visitor is None:
+            return node
+        return visitor(node)
+
+    def _clone(self, node):
+        clone = node.__class__()
+        for name in getattr(clone, '_attributes', ()):
+            try:
+                setattr(clone, 'name', getattr(node, name))
+            except AttributeError:
+                pass
+        for name in clone._fields:
+            try:
+                value = getattr(node, name)
+            except AttributeError:
+                pass
+            else:
+                if value is None:
+                    pass
+                elif isinstance(value, list):
+                    value = [self.visit(x) for x in value]
+                elif isinstance(value, tuple):
+                    value = tuple(self.visit(x) for x in value)
+                else: 
+                    value = self.visit(value)
+                setattr(clone, name, value)
+        return clone
+
+    visit_Module = _clone
+    visit_Interactive = _clone
+    visit_Expression = _clone
+    visit_Suite = _clone
+
+    visit_FunctionDef = _clone
+    visit_ClassDef = _clone
+    visit_Return = _clone
+    visit_Delete = _clone
+    visit_Assign = _clone
+    visit_AugAssign = _clone
+    visit_Print = _clone
+    visit_For = _clone
+    visit_While = _clone
+    visit_If = _clone
+    visit_With = _clone
+    visit_Raise = _clone
+    visit_TryExcept = _clone
+    visit_TryFinally = _clone
+    visit_Assert = _clone
+
+    visit_Import = _clone
+    visit_ImportFrom = _clone
+    visit_Exec = _clone
+    visit_Global = _clone
+    visit_Expr = _clone
+    # Pass, Break, Continue don't need to be copied
+
+    visit_BoolOp = _clone
+    visit_BinOp = _clone
+    visit_UnaryOp = _clone
+    visit_Lambda = _clone
+    visit_IfExp = _clone
+    visit_Dict = _clone
+    visit_ListComp = _clone
+    visit_GeneratorExp = _clone
+    visit_Yield = _clone
+    visit_Compare = _clone
+    visit_Call = _clone
+    visit_Repr = _clone
+    # Num, Str don't need to be copied
+
+    visit_Attribute = _clone
+    visit_Subscript = _clone
+    visit_Name = _clone
+    visit_List = _clone
+    visit_Tuple = _clone
+
+    visit_comprehension = _clone
+    visit_excepthandler = _clone
+    visit_arguments = _clone
+    visit_keyword = _clone
+    visit_alias = _clone
+
+    visit_Slice = _clone
+    visit_ExtSlice = _clone
+    visit_Index = _clone
+
+    del _clone
--- a/genshi/template/directives.py
+++ b/genshi/template/directives.py
@@ -13,14 +13,13 @@
 
 """Implementation of the various template directives."""
 
-import compiler
-
 from genshi.core import QName, Stream
 from genshi.path import Path
 from genshi.template.base import TemplateRuntimeError, TemplateSyntaxError, \
                                  EXPR, _apply_directives, _eval_expr, \
                                  _exec_suite
-from genshi.template.eval import Expression, ExpressionASTTransformer, _parse
+from genshi.template.eval import Expression, ExpressionASTTransformer, \
+                                 _ast, _parse
 
 __all__ = ['AttrsDirective', 'ChooseDirective', 'ContentDirective',
            'DefDirective', 'ForDirective', 'IfDirective', 'MatchDirective',
@@ -118,14 +117,14 @@
 
 
 def _assignment(ast):
-    """Takes the AST representation of an assignment, and returns a function
-    that applies the assignment of a given value to a dictionary.
+    """Takes the AST representation of an assignment, and returns a
+    function that applies the assignment of a given value to a dictionary.
     """
     def _names(node):
-        if isinstance(node, (compiler.ast.AssTuple, compiler.ast.Tuple)):
-            return tuple([_names(child) for child in node.nodes])
-        elif isinstance(node, (compiler.ast.AssName, compiler.ast.Name)):
-            return node.name
+        if isinstance(node, _ast.Tuple):
+            return tuple([_names(child) for child in node.elts])
+        elif isinstance(node, _ast.Name):
+            return node.id
     def _assign(data, value, names=_names(ast)):
         if type(names) is tuple:
             for idx in range(len(names)):
@@ -259,28 +258,27 @@
 
     def __init__(self, args, template, namespaces=None, lineno=-1, offset=-1):
         Directive.__init__(self, None, template, namespaces, lineno, offset)
-        ast = _parse(args).node
+        ast = _parse(args).body
         self.args = []
         self.star_args = None
         self.dstar_args = None
         self.defaults = {}
-        if isinstance(ast, compiler.ast.CallFunc):
-            self.name = ast.node.name
+        if isinstance(ast, _ast.Call):
+            self.name = ast.func.id
             for arg in ast.args:
-                if isinstance(arg, compiler.ast.Keyword):
-                    self.args.append(arg.name)
-                    self.defaults[arg.name] = Expression(arg.expr,
-                                                         template.filepath,
-                                                         lineno,
-                                                         lookup=template.lookup)
-                else:
-                    self.args.append(arg.name)
-            if ast.star_args:
-                self.star_args = ast.star_args.name
-            if ast.dstar_args:
-                self.dstar_args = ast.dstar_args.name
+                # only names
+                self.args.append(arg.id)
+            for kwd in ast.keywords:
+                self.args.append(kwd.arg)
+                exp = Expression(kwd.value, template.filepath,
+                                 lineno, lookup=template.lookup)
+                self.defaults[kwd.arg] = exp
+            if getattr(ast, 'starargs', None):
+                self.star_args = ast.starargs.id
+            if getattr(ast, 'kwargs', None):
+                self.dstar_args = ast.kwargs.id
         else:
-            self.name = ast.name
+            self.name = ast.id
 
     def attach(cls, template, stream, value, namespaces, pos):
         if type(value) is dict:
@@ -347,7 +345,7 @@
         assign, value = value.split(' in ', 1)
         ast = _parse(assign, 'exec')
         value = 'iter(%s)' % value.strip()
-        self.assign = _assignment(ast.node.nodes[0].expr)
+        self.assign = _assignment(ast.body[0].value)
         self.filename = template.filepath
         Directive.__init__(self, value, template, namespaces, lineno, offset)
 
@@ -695,20 +693,18 @@
 
     def __init__(self, value, template, namespaces=None, lineno=-1, offset=-1):
         Directive.__init__(self, None, template, namespaces, lineno, offset)
-        self.vars = [] 
-        value = value.strip() 
+        self.vars = []
+        value = value.strip()
         try:
-            ast = _parse(value, 'exec').node 
-            for node in ast.nodes: 
-                if isinstance(node, compiler.ast.Discard): 
-                    continue 
-                elif not isinstance(node, compiler.ast.Assign): 
-                    raise TemplateSyntaxError('only assignment allowed in ' 
-                                              'value of the "with" directive', 
-                                              template.filepath, lineno, offset) 
-                self.vars.append(([_assignment(n) for n in node.nodes], 
-                                  Expression(node.expr, template.filepath, 
-                                             lineno, lookup=template.lookup))) 
+            ast = _parse(value, 'exec')
+            for node in ast.body:
+                if not isinstance(node, _ast.Assign):
+                    raise TemplateSyntaxError('only assignment allowed in '
+                                              'value of the "with" directive',
+                                              template.filepath, lineno, offset)
+                self.vars.append(([_assignment(n) for n in node.targets],
+                                  Expression(node.value, template.filepath,
+                                             lineno, lookup=template.lookup)))
         except SyntaxError, err:
             err.msg += ' in expression "%s" of "%s" directive' % (value,
                                                                   self.tagname)
@@ -725,7 +721,7 @@
     def __call__(self, stream, directives, ctxt, **vars):
         frame = {}
         ctxt.push(frame)
-        for targets, expr in self.vars: 
+        for targets, expr in self.vars:
             value = _eval_expr(expr, ctxt, **vars)
             for assign in targets:
                 assign(frame, value)
--- a/genshi/template/eval.py
+++ b/genshi/template/eval.py
@@ -14,13 +14,13 @@
 """Support for "safe" evaluation of Python expressions."""
 
 import __builtin__
-from compiler import ast, parse
-from compiler.pycodegen import ExpressionCodeGenerator, ModuleCodeGenerator
-import new
+
 from textwrap import dedent
 from types import CodeType
 
 from genshi.core import Markup
+from genshi.template.astutil import ASTTransformer, ASTCodeGenerator, \
+                                    _ast, parse
 from genshi.template.base import TemplateRuntimeError
 from genshi.util import flatten
 
@@ -28,6 +28,7 @@
            'Undefined', 'UndefinedError']
 __docformat__ = 'restructuredtext en'
 
+
 # Check for a Python 2.4 bug in the eval loop
 has_star_import_bug = False
 try:
@@ -38,6 +39,7 @@
     has_star_import_bug = True
 del _FakeMapping
 
+
 def _star_import_patch(mapping, modname):
     """This function is used as helper if a Python version with a broken
     star-import opcode is in use.
@@ -74,13 +76,15 @@
             self.source = source
             node = _parse(source, mode=self.mode)
         else:
-            assert isinstance(source, ast.Node), \
+            assert isinstance(source, _ast.AST), \
                 'Expected string or AST node, but got %r' % source
             self.source = '?'
             if self.mode == 'eval':
-                node = ast.Expression(source)
+                node = _ast.Expression()
+                node.body = source
             else:
-                node = ast.Module(None, source)
+                node = _ast.Module()
+                node.body = [source]
 
         self.ast = node
         self.code = _compile(node, self.source, mode=self.mode,
@@ -103,7 +107,7 @@
     def __setstate__(self, state):
         self.source = state['source']
         self.ast = state['ast']
-        self.code = new.code(0, *state['code'])
+        self.code = CodeType(0, *state['code'])
         self._globals = state['lookup'].globals
 
     def __eq__(self, other):
@@ -414,26 +418,26 @@
         source = '\xef\xbb\xbf' + source.encode('utf-8')
     return parse(source, mode)
 
+
 def _compile(node, source=None, mode='eval', filename=None, lineno=-1,
              xform=None):
-    if xform is None:
-        xform = {'eval': ExpressionASTTransformer}.get(mode,
-                                                       TemplateASTTransformer)
-    tree = xform().visit(node)
     if isinstance(filename, unicode):
         # unicode file names not allowed for code objects
         filename = filename.encode('utf-8', 'replace')
     elif not filename:
         filename = '<string>'
-    tree.filename = filename
     if lineno <= 0:
         lineno = 1
 
+    if xform is None:
+        xform = {
+            'eval': ExpressionASTTransformer
+        }.get(mode, TemplateASTTransformer)
+    tree = xform().visit(node)
+
     if mode == 'eval':
-        gen = ExpressionCodeGenerator(tree)
         name = '<Expression %r>' % (source or '?')
     else:
-        gen = ModuleCodeGenerator(tree)
         lines = source.splitlines()
         if not lines:
             extract = ''
@@ -442,267 +446,36 @@
         if len(lines) > 1:
             extract += ' ...'
         name = '<Suite %r>' % (extract)
-    gen.optimized = True
-    code = gen.getCode()
+    new_source = ASTCodeGenerator(tree).code
+    code = compile(new_source, filename, mode)
 
-    # We'd like to just set co_firstlineno, but it's readonly. So we need to
-    # clone the code object while adjusting the line number
-    return CodeType(0, code.co_nlocals, code.co_stacksize,
-                    code.co_flags | 0x0040, code.co_code, code.co_consts,
-                    code.co_names, code.co_varnames, filename, name, lineno,
-                    code.co_lnotab, (), ())
+    try:
+        # We'd like to just set co_firstlineno, but it's readonly. So we need
+        # to clone the code object while adjusting the line number
+        return CodeType(0, code.co_nlocals, code.co_stacksize,
+                        code.co_flags | 0x0040, code.co_code, code.co_consts,
+                        code.co_names, code.co_varnames, filename, name,
+                        lineno, code.co_lnotab, (), ())
+    except RuntimeError:
+        return code
+
+
+def _new(class_, *args, **kwargs):
+    ret = class_()
+    for attr, value in zip(ret._fields, args):
+        if attr in kwargs:
+            raise ValueError('Field set both in args and kwargs')
+        setattr(ret, attr, value)
+    for attr, value in kwargs:
+        setattr(ret, attr, value)
+    return ret
+
 
 BUILTINS = __builtin__.__dict__.copy()
 BUILTINS.update({'Markup': Markup, 'Undefined': Undefined})
 CONSTANTS = frozenset(['False', 'True', 'None', 'NotImplemented', 'Ellipsis'])
 
 
-class ASTTransformer(object):
-    """General purpose base class for AST transformations.
-    
-    Every visitor method can be overridden to return an AST node that has been
-    altered or replaced in some way.
-    """
-
-    def visit(self, node):
-        if node is None:
-            return None
-        if type(node) is tuple:
-            return tuple([self.visit(n) for n in node])
-        visitor = getattr(self, 'visit%s' % node.__class__.__name__,
-                          self._visitDefault)
-        return visitor(node)
-
-    def _clone(self, node, *args):
-        lineno = getattr(node, 'lineno', None)
-        node = node.__class__(*args)
-        if lineno is not None:
-            node.lineno = lineno
-        if isinstance(node, (ast.Class, ast.Function, ast.Lambda,
-                             ast.GenExpr)):
-            node.filename = '<string>' # workaround for bug in pycodegen
-        return node
-
-    def _visitDefault(self, node):
-        return node
-
-    def visitExpression(self, node):
-        return self._clone(node, self.visit(node.node))
-
-    def visitModule(self, node):
-        return self._clone(node, node.doc, self.visit(node.node))
-
-    def visitStmt(self, node):
-        return self._clone(node, [self.visit(x) for x in node.nodes])
-
-    # Classes, Functions & Accessors
-
-    def visitCallFunc(self, node):
-        return self._clone(node, self.visit(node.node),
-            [self.visit(x) for x in node.args],
-            node.star_args and self.visit(node.star_args) or None,
-            node.dstar_args and self.visit(node.dstar_args) or None
-        )
-
-    def visitClass(self, node):
-        return self._clone(node, node.name, [self.visit(x) for x in node.bases],
-            node.doc, self.visit(node.code)
-        )
-
-    def visitFrom(self, node):
-        if not has_star_import_bug or node.names != [('*', None)]:
-            # This is a Python 2.4 bug. Only if we have a broken Python
-            # version we have to apply the hack
-            return node
-        return ast.Discard(ast.CallFunc(
-            ast.Name('_star_import_patch'),
-            [ast.Name('__data__'), ast.Const(node.modname)], None, None
-        ), lineno=node.lineno)
-
-    def visitFunction(self, node):
-        args = []
-        if hasattr(node, 'decorators'):
-            args.append(self.visit(node.decorators))
-        return self._clone(node, *args + [
-            node.name,
-            node.argnames,
-            [self.visit(x) for x in node.defaults],
-            node.flags,
-            node.doc,
-            self.visit(node.code)
-        ])
-
-    def visitGetattr(self, node):
-        return self._clone(node, self.visit(node.expr), node.attrname)
-
-    def visitLambda(self, node):
-        node = self._clone(node, node.argnames,
-            [self.visit(x) for x in node.defaults], node.flags,
-            self.visit(node.code)
-        )
-        return node
-
-    def visitSubscript(self, node):
-        return self._clone(node, self.visit(node.expr), node.flags,
-            [self.visit(x) for x in node.subs]
-        )
-
-    # Statements
-
-    def visitAssert(self, node):
-        return self._clone(node, self.visit(node.test), self.visit(node.fail))
-
-    def visitAssign(self, node):
-        return self._clone(node, [self.visit(x) for x in node.nodes],
-            self.visit(node.expr)
-        )
-
-    def visitAssAttr(self, node):
-        return self._clone(node, self.visit(node.expr), node.attrname,
-            node.flags
-        )
-
-    def visitAugAssign(self, node):
-        return self._clone(node, self.visit(node.node), node.op,
-            self.visit(node.expr)
-        )
-
-    def visitDecorators(self, node):
-        return self._clone(node, [self.visit(x) for x in node.nodes])
-
-    def visitExec(self, node):
-        return self._clone(node, self.visit(node.expr), self.visit(node.locals),
-            self.visit(node.globals)
-        )
-
-    def visitFor(self, node):
-        return self._clone(node, self.visit(node.assign), self.visit(node.list),
-            self.visit(node.body), self.visit(node.else_)
-        )
-
-    def visitIf(self, node):
-        return self._clone(node, [self.visit(x) for x in node.tests],
-            self.visit(node.else_)
-        )
-
-    def _visitPrint(self, node):
-        return self._clone(node, [self.visit(x) for x in node.nodes],
-            self.visit(node.dest)
-        )
-    visitPrint = visitPrintnl = _visitPrint
-
-    def visitRaise(self, node):
-        return self._clone(node, self.visit(node.expr1), self.visit(node.expr2),
-            self.visit(node.expr3)
-        )
-
-    def visitReturn(self, node):
-        return self._clone(node, self.visit(node.value))
-
-    def visitTryExcept(self, node):
-        return self._clone(node, self.visit(node.body), self.visit(node.handlers),
-            self.visit(node.else_)
-        )
-
-    def visitTryFinally(self, node):
-        return self._clone(node, self.visit(node.body), self.visit(node.final))
-
-    def visitWhile(self, node):
-        return self._clone(node, self.visit(node.test), self.visit(node.body),
-            self.visit(node.else_)
-        )
-
-    def visitWith(self, node):
-        return self._clone(node, self.visit(node.expr),
-            [self.visit(x) for x in node.vars], self.visit(node.body)
-        )
-
-    def visitYield(self, node):
-        return self._clone(node, self.visit(node.value))
-
-    # Operators
-
-    def _visitBoolOp(self, node):
-        return self._clone(node, [self.visit(x) for x in node.nodes])
-    visitAnd = visitOr = visitBitand = visitBitor = visitBitxor = _visitBoolOp
-    visitAssTuple = visitAssList = _visitBoolOp
-
-    def _visitBinOp(self, node):
-        return self._clone(node,
-            (self.visit(node.left), self.visit(node.right))
-        )
-    visitAdd = visitSub = _visitBinOp
-    visitDiv = visitFloorDiv = visitMod = visitMul = visitPower = _visitBinOp
-    visitLeftShift = visitRightShift = _visitBinOp
-
-    def visitCompare(self, node):
-        return self._clone(node, self.visit(node.expr),
-            [(op, self.visit(n)) for op, n in  node.ops]
-        )
-
-    def _visitUnaryOp(self, node):
-        return self._clone(node, self.visit(node.expr))
-    visitUnaryAdd = visitUnarySub = visitNot = visitInvert = _visitUnaryOp
-    visitBackquote = visitDiscard = _visitUnaryOp
-
-    def visitIfExp(self, node):
-        return self._clone(node, self.visit(node.test), self.visit(node.then),
-            self.visit(node.else_)
-        )
-
-    # Identifiers, Literals and Comprehensions
-
-    def visitDict(self, node):
-        return self._clone(node, 
-            [(self.visit(k), self.visit(v)) for k, v in node.items]
-        )
-
-    def visitGenExpr(self, node):
-        return self._clone(node, self.visit(node.code))
-
-    def visitGenExprFor(self, node):
-        return self._clone(node, self.visit(node.assign), self.visit(node.iter),
-            [self.visit(x) for x in node.ifs]
-        )
-
-    def visitGenExprIf(self, node):
-        return self._clone(node, self.visit(node.test))
-
-    def visitGenExprInner(self, node):
-        quals = [self.visit(x) for x in node.quals]
-        return self._clone(node, self.visit(node.expr), quals)
-
-    def visitKeyword(self, node):
-        return self._clone(node, node.name, self.visit(node.expr))
-
-    def visitList(self, node):
-        return self._clone(node, [self.visit(n) for n in node.nodes])
-
-    def visitListComp(self, node):
-        quals = [self.visit(x) for x in node.quals]
-        return self._clone(node, self.visit(node.expr), quals)
-
-    def visitListCompFor(self, node):
-        return self._clone(node, self.visit(node.assign), self.visit(node.list),
-            [self.visit(x) for x in node.ifs]
-        )
-
-    def visitListCompIf(self, node):
-        return self._clone(node, self.visit(node.test))
-
-    def visitSlice(self, node):
-        return self._clone(node, self.visit(node.expr), node.flags,
-            node.lower and self.visit(node.lower) or None,
-            node.upper and self.visit(node.upper) or None
-        )
-
-    def visitSliceobj(self, node):
-        return self._clone(node, [self.visit(x) for x in node.nodes])
-
-    def visitTuple(self, node):
-        return self._clone(node, [self.visit(n) for n in node.nodes])
-
-
 class TemplateASTTransformer(ASTTransformer):
     """Concrete AST transformer that implements the AST transformations needed
     for code embedded in templates.
@@ -711,88 +484,110 @@
     def __init__(self):
         self.locals = [CONSTANTS]
 
-    def visitConst(self, node):
-        if isinstance(node.value, str):
+    def _extract_names(self, node):
+        arguments = set()
+        def _process(node):
+            if isinstance(node, _ast.Name):
+                arguments.add(node.id)
+            elif isinstance(node, _ast.Tuple):
+                for elt in node.elts:
+                    _process(node)
+        for arg in node.args:
+            _process(arg)
+        if getattr(node, 'varargs', None):
+            arguments.add(node.args.varargs)
+        if getattr(node, 'kwargs', None):
+            arguments.add(node.args.kwargs)
+        return arguments
+
+    def visit_Str(self, node):
+        if isinstance(node.s, str):
             try: # If the string is ASCII, return a `str` object
-                node.value.decode('ascii')
+                node.s.decode('ascii')
             except ValueError: # Otherwise return a `unicode` object
-                return ast.Const(node.value.decode('utf-8'))
-        return node
-
-    def visitAssName(self, node):
-        if len(self.locals) > 1:
-            self.locals[-1].add(node.name)
+                return _new(_ast.Str, node.s.decode('utf-8'))
         return node
 
-    def visitAugAssign(self, node):
-        if isinstance(node.node, ast.Name) \
-                and node.node.name not in flatten(self.locals):
-            name = node.node.name
-            node.node = ast.Subscript(ast.Name('__data__'), 'OP_APPLY',
-                                      [ast.Const(name)])
-            node.expr = self.visit(node.expr)
-            return ast.If([
-                (ast.Compare(ast.Const(name), [('in', ast.Name('__data__'))]),
-                 ast.Stmt([node]))],
-                ast.Stmt([ast.Raise(ast.CallFunc(ast.Name('UndefinedError'),
-                                                 [ast.Const(name)]),
-                                    None, None)]))
-        else:
-            return ASTTransformer.visitAugAssign(self, node)
-
-    def visitClass(self, node):
+    def visit_ClassDef(self, node):
         if len(self.locals) > 1:
             self.locals[-1].add(node.name)
         self.locals.append(set())
         try:
-            return ASTTransformer.visitClass(self, node)
-        finally:
-            self.locals.pop()
-
-    def visitFor(self, node):
-        self.locals.append(set())
-        try:
-            return ASTTransformer.visitFor(self, node)
-        finally:
-            self.locals.pop()
-
-    def visitFunction(self, node):
-        if len(self.locals) > 1:
-            self.locals[-1].add(node.name)
-        self.locals.append(set(node.argnames))
-        try:
-            return ASTTransformer.visitFunction(self, node)
+            return ASTTransformer.visit_ClassDef(self, node)
         finally:
             self.locals.pop()
 
-    def visitGenExpr(self, node):
+    def visit_For(self, node):
         self.locals.append(set())
         try:
-            return ASTTransformer.visitGenExpr(self, node)
-        finally:
-            self.locals.pop()
-
-    def visitLambda(self, node):
-        self.locals.append(set(flatten(node.argnames)))
-        try:
-            return ASTTransformer.visitLambda(self, node)
+            return ASTTransformer.visit_For(self, node)
         finally:
             self.locals.pop()
 
-    def visitListComp(self, node):
-        self.locals.append(set())
+    def visit_ImportFrom(self, node):
+        if not has_star_import_bug or [a.name for a in node.names] != ['*']:
+            # This is a Python 2.4 bug. Only if we have a broken Python
+            # version we have to apply the hack
+            return node
+        return _new(_ast.Expr, _new(_ast.Call,
+            _new(_ast.Name, '_star_import_patch'), [
+                _new(_ast.Name, '__data__'),
+                _new(_ast.Str, node.module)
+            ], (), ()))
+
+    def visit_FunctionDef(self, node):
+        if len(self.locals) > 1:
+            self.locals[-1].add(node.name)
+
+        self.locals.append(self._extract_names(node.args))
         try:
-            return ASTTransformer.visitListComp(self, node)
+            return ASTTransformer.visit_FunctionDef(self, node)
         finally:
             self.locals.pop()
 
-    def visitName(self, node):
+    # GeneratorExp(expr elt, comprehension* generators)
+    def visit_GeneratorExp(self, node):
+        gens = []
+        # need to visit them in inverse order
+        for generator in node.generators[::-1]:
+            # comprehension = (expr target, expr iter, expr* ifs)
+            self.locals.append(set())
+            gen = _new(_ast.comprehension, self.visit(generator.target),
+                            self.visit(generator.iter),
+                            [self.visit(if_) for if_ in generator.ifs])
+            gens.append(gen)
+        gens.reverse()
+
+        # use node.__class__ to make it reusable as ListComp
+        ret = _new(node.__class__, self.visit(node.elt), gens)
+        #delete inserted locals
+        del self.locals[-len(node.generators):]
+        return ret
+
+    # ListComp(expr elt, comprehension* generators)
+    visit_ListComp = visit_GeneratorExp
+
+    def visit_Lambda(self, node):
+        self.locals.append(self._extract_names(node.args))
+        try:
+            return ASTTransformer.visit_Lambda(self, node)
+        finally:
+            self.locals.pop()
+
+    def visit_Name(self, node):
         # If the name refers to a local inside a lambda, list comprehension, or
         # generator expression, leave it alone
-        if node.name not in flatten(self.locals):
+        if isinstance(node.ctx, _ast.Load) and \
+                node.id not in flatten(self.locals):
             # Otherwise, translate the name ref into a context lookup
-            func_args = [ast.Name('__data__'), ast.Const(node.name)]
-            node = ast.CallFunc(ast.Name('_lookup_name'), func_args)
+            name = _new(_ast.Name, '_lookup_name', _ast.Load())
+            namearg = _new(_ast.Name, '__data__', _ast.Load())
+            strarg = _new(_ast.Str, node.id)
+            node = _new(_ast.Call, name, [namearg, strarg], [])
+        elif isinstance(node.ctx, _ast.Store):
+            if len(self.locals) > 1:
+                self.locals[-1].add(node.id)
+
         return node
 
 
@@ -801,14 +596,22 @@
     for code embedded in templates.
     """
 
-    def visitGetattr(self, node):
-        return ast.CallFunc(ast.Name('_lookup_attr'), [
-            self.visit(node.expr),
-            ast.Const(node.attrname)
-        ])
+    def visit_Attribute(self, node):
+        if not isinstance(node.ctx, _ast.Load):
+            return ASTTransformer.visit_Attribute(self, node)
 
-    def visitSubscript(self, node):
-        return ast.CallFunc(ast.Name('_lookup_item'), [
-            self.visit(node.expr),
-            ast.Tuple([self.visit(sub) for sub in node.subs])
-        ])
+        func = _new(_ast.Name, '_lookup_attr', _ast.Load())
+        args = [self.visit(node.value), _new(_ast.Str, node.attr)]
+        return _new(_ast.Call, func, args, [])
+
+    def visit_Subscript(self, node):
+        if not isinstance(node.ctx, _ast.Load) or \
+                not isinstance(node.slice, _ast.Index):
+            return ASTTransformer.visit_Subscript(self, node)
+
+        func = _new(_ast.Name, '_lookup_item', _ast.Load())
+        args = [
+            self.visit(node.value),
+            _new(_ast.Tuple, (self.visit(node.slice.value),), _ast.Load())
+        ]
+        return _new(_ast.Call, func, args, [])
new file mode 100755
--- /dev/null
+++ b/scripts/ast_generator.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python2.5
+
+"""Script to that automatically generates genshi/templates/_astpy24.py.
+Be sure to run this with a Python 2.5 interpreter.
+"""
+
+import _ast
+
+done = set()
+
+IGNORE_ATTRS = ('__module__', '__dict__', '__weakref__', '__setattr__',
+                '__new__', '__getattribute__', '__reduce__', '__delattr__',
+                '__init__')
+
+def print_class(cls):
+    bnames = []
+    for base in cls.__bases__:
+        if base.__module__ == '_ast':
+            if base not in done:
+                print_class(base)
+            bnames.append(base.__name__)
+        elif base.__module__ == '__builtin__':
+            bnames.append("%s"%base.__name__)
+        else:
+            bnames.append("%s.%s"%(base.__module__,base.__name__))
+    print "class %s(%s):"%(cls.__name__, ", ".join(bnames))
+    written = False
+    for attr in cls.__dict__:
+        if attr not in IGNORE_ATTRS:
+            written = True
+            print "\t%s = %s"%(attr, repr(cls.__dict__[attr]),)
+    if not written:
+        print "\tpass"
+    done.add(cls)
+
+print "# Generated automatically, please do not edit"
+print "# Generator can be found in Genshi SVN, scripts/ast-generator.py"
+print
+print "__version__ = %s" % _ast.__version__
+print
+
+for name in dir(_ast):
+    cls = getattr(_ast, name)
+    if cls.__class__ is type:
+        print_class(cls)
+        print
Copyright (C) 2012-2017 Edgewall Software