added first working pieces of compiler

--HG--
branch : trunk
diff --git a/jinja2/compiler.py b/jinja2/compiler.py
new file mode 100644
index 0000000..330db56
--- /dev/null
+++ b/jinja2/compiler.py
@@ -0,0 +1,380 @@
+# -*- coding: utf-8 -*-
+"""
+    jinja2.compiler
+    ~~~~~~~~~~~~~~~
+
+    Compiles nodes into python code.
+
+    :copyright: Copyright 2008 by Armin Ronacher.
+    :license: GNU GPL.
+"""
+from random import randrange
+from operator import xor
+from cStringIO import StringIO
+from jinja2 import nodes
+from jinja2.visitor import NodeVisitor, NodeTransformer
+from jinja2.exceptions import TemplateAssertionError
+
+
+operators = {
+    'eq':       '==',
+    'ne':       '!=',
+    'gt':       '>',
+    'gteq':     '>=',
+    'lt':       '<',
+    'lteq':     '<=',
+    'in':       'in',
+    'notin':    'not in'
+}
+
+
+def generate(node, filename, stream=None):
+    is_child = node.find(nodes.Extends) is not None
+    generator = CodeGenerator(is_child, filename, stream)
+    generator.visit(node)
+    if stream is None:
+        return generator.stream.getvalue()
+
+
+class Identifiers(object):
+    """Tracks the status of identifiers in frames."""
+
+    def __init__(self):
+        # variables that are known to be declared (probably from outer
+        # frames or because they are special for the frame)
+        self.declared = set()
+
+        # names that are accessed without being explicitly declared by
+        # this one or any of the outer scopes.  Names can appear both in
+        # declared and undeclared.
+        self.undeclared = set()
+
+        # names that are declared locally
+        self.declared_locally = set()
+
+        # names that are declared by parameters
+        self.declared_parameter = set()
+
+    def add_special(self, name):
+        """Register a special name like `loop`."""
+        self.undeclared.discard(name)
+        self.declared.add(name)
+
+    def is_declared(self, name):
+        """Check if a name is declared in this or an outer scope."""
+        return name in self.declared or name in self.declared_locally or \
+               name in self.declared_parameter
+
+    def find_shadowed(self):
+        """Find all the shadowed names."""
+        return self.declared & (self.declared_locally | self.declared_parameter)
+
+
+class Frame(object):
+
+    def __init__(self, parent=None):
+        self.identifiers = Identifiers()
+        self.parent = parent
+        if parent is not None:
+            self.identifiers.declared.update(
+                parent.identifiers.declared |
+                parent.identifiers.undeclared |
+                parent.identifiers.declared_locally |
+                parent.identifiers.declared_parameter
+            )
+
+    def inspect(self, nodes):
+        """Walk the node and check for identifiers."""
+        visitor = FrameIdentifierVisitor(self.identifiers)
+        for node in nodes:
+            visitor.visit(node)
+
+    def inner(self):
+        """Return an inner frame."""
+        return Frame(self)
+
+
+class FrameIdentifierVisitor(NodeVisitor):
+    """A visitor for `Frame.inspect`."""
+
+    def __init__(self, identifiers):
+        self.identifiers = identifiers
+
+    def visit_Name(self, node):
+        """All assignments to names go through this function."""
+        if node.ctx in ('store', 'param'):
+            self.identifiers.declared_locally.add(node.name)
+        elif node.ctx == 'load':
+            if not self.identifiers.is_declared(node.name):
+                self.identifiers.undeclared.add(node.name)
+
+    def visit_Macro(self, node):
+        """Macros set local."""
+        self.identifiers.declared_locally.add(node.name)
+
+    # stop traversing at instructions that have their own scope.
+    visit_Block = visit_Call = visit_FilterBlock = \
+        visit_For = lambda s, n: None
+
+
+class CodeGenerator(NodeVisitor):
+
+    def __init__(self, is_child, filename, stream=None):
+        if stream is None:
+            stream = StringIO()
+        self.is_child = is_child
+        self.filename = filename
+        self.stream = stream
+        self.blocks = {}
+        self.indentation = 0
+        self.new_lines = 0
+        self.last_identifier = 0
+        self._last_line = 0
+        self._first_write = True
+
+    def temporary_identifier(self):
+        self.last_identifier += 1
+        return 't%d' % self.last_identifier
+
+    def indent(self):
+        self.indentation += 1
+
+    def outdent(self):
+        self.indentation -= 1
+
+    def blockvisit(self, nodes, frame, force_generator=False):
+        self.indent()
+        if force_generator:
+            self.writeline('if 0: yield None')
+        for node in nodes:
+            self.visit(node, frame)
+        self.outdent()
+
+    def write(self, x):
+        if self.new_lines:
+            if not self._first_write:
+                self.stream.write('\n' * self.new_lines)
+            self._first_write = False
+            self.stream.write('    ' * self.indentation)
+            self.new_lines = 0
+        self.stream.write(x)
+
+    def writeline(self, x, node=None, extra=0):
+        self.newline(node, extra)
+        self.write(x)
+
+    def newline(self, node=None, extra=0):
+        self.new_lines = max(self.new_lines, 1 + extra)
+        if node is not None and node.lineno != self._last_line:
+            self.write('# line: %s' % node.lineno)
+            self.new_lines = 1
+            self._last_line = node.lineno
+
+    def pull_locals(self, frame, no_indent=False):
+        if not no_indent:
+            self.indent()
+        for name in frame.identifiers.undeclared:
+            self.writeline('l_%s = context[%r]' % (name, name))
+        if not no_indent:
+            self.outdent()
+
+    # -- Visitors
+
+    def visit_Template(self, node, frame=None):
+        assert frame is None, 'no root frame allowed'
+        self.writeline('from jinja2.runtime import *')
+        self.writeline('filename = %r' % self.filename)
+        self.writeline('context = TemplateContext(global_context, '
+                       'make_undefined, filename)')
+
+        # generate the body render function.
+        self.writeline('def body(context=context):', extra=1)
+        frame = Frame()
+        frame.inspect(node.body)
+        self.pull_locals(frame)
+        self.blockvisit(node.body, frame, True)
+
+        # top level changes to locals are pushed back to the
+        # context of *this* template for include.
+        self.indent()
+        self.writeline('context.from_locals(locals())')
+        self.outdent()
+
+        # at this point we now have the blocks collected and can visit them too.
+        for name, block in self.blocks.iteritems():
+            block_frame = Frame()
+            block_frame.inspect(block.body)
+            self.writeline('def block_%s(context=context):' % name, block, 1)
+            self.pull_locals(block_frame)
+            self.blockvisit(block.body, block_frame, True)
+
+    def visit_Block(self, node, frame):
+        """Call a block and register it for the template."""
+        if node.name in self.blocks:
+            raise TemplateAssertionError("the block '%s' was already defined" %
+                                         node.name, node.lineno,
+                                         self.filename)
+        self.blocks[node.name] = node
+        self.writeline('for event in block_%s():' % node.name)
+        self.indent()
+        self.writeline('yield event')
+        self.outdent()
+
+    def visit_Extends(self, node, frame):
+        """Calls the extender."""
+        self.writeline('extends(', node, 1)
+        self.visit(node.template)
+        self.write(', globals())')
+
+    def visit_For(self, node, frame):
+        loop_frame = frame.inner()
+        loop_frame.inspect(node.iter_child_nodes())
+        loop_frame.identifiers.add_special('loop')
+        extended_loop = bool(node.else_) or \
+                        'loop' in loop_frame.identifiers.undeclared
+
+        # make sure we "backup" overridden, local identifiers
+        # TODO: we should probably optimize this and check if the
+        # identifier is in use afterwards.
+        aliases = {}
+        for name in loop_frame.identifiers.find_shadowed():
+            aliases[name] = ident = self.temporary_identifier()
+            self.writeline('%s = l_%s' % (ident, name))
+
+        self.pull_locals(loop_frame, True)
+
+        self.newline(node)
+        if node.else_:
+            self.writeline('l_loop = None')
+        self.write('for ')
+        self.visit(node.target, loop_frame)
+        self.write(extended_loop and ', l_loop in looper(' or ' in ')
+        self.visit(node.iter, loop_frame)
+        self.write(extended_loop and '):' or ':')
+        self.blockvisit(node.body, loop_frame)
+
+        if node.else_:
+            self.writeline('if l_loop is None:')
+            self.blockvisit(node.else_, loop_frame)
+
+        # reset the aliases and clean them up
+        for name, alias in aliases.iteritems():
+            self.writeline('l_%s = %s; del %s' % (name, alias, alias))
+
+    def visit_If(self, node, frame):
+        self.writeline('if ', node)
+        self.visit(node.test, frame)
+        self.write(':')
+        self.blockvisit(node.body, frame)
+        if node.else_:
+            self.writeline('else:')
+            self.blockvisit(node.else_, frame)
+
+    def visit_ExprStmt(self, node, frame):
+        self.newline(node)
+        self.visit(node, frame)
+
+    def visit_Output(self, node, frame):
+        self.newline(node)
+
+        # try to evaluate as many chunks as possible into a static
+        # string at compile time.
+        body = []
+        for child in node.nodes:
+            try:
+                const = unicode(child.as_const())
+            except:
+                body.append(child)
+                continue
+            if body and isinstance(body[-1], list):
+                body[-1].append(const)
+            else:
+                body.append([const])
+
+        # if we have less than 3 nodes we just yield them
+        if len(body) < 3:
+            for item in body:
+                if isinstance(item, list):
+                    self.writeline('yield %s' % repr(u''.join(item)))
+                else:
+                    self.newline(item)
+                    self.write('yield unicode(')
+                    self.visit(item, frame)
+                    self.write(')')
+
+        # otherwise we create a format string as this is faster in that case
+        else:
+            format = []
+            arguments = []
+            for item in body:
+                if isinstance(item, list):
+                    format.append(u''.join(item).replace('%', '%%'))
+                else:
+                    format.append('%s')
+                    arguments.append(item)
+            self.writeline('yield %r %% (' % u''.join(format))
+            idx = -1
+            for idx, argument in enumerate(arguments):
+                if idx:
+                    self.write(', ')
+                self.visit(argument, frame)
+            self.write(idx == 0 and ',)' or ')')
+
+    def visit_Name(self, node, frame):
+        # at this point we should only have locals left as the
+        # blocks, macros and template body ensure that they are set.
+        self.write('l_' + node.name)
+
+    def visit_Const(self, node, frame):
+        val = node.value
+        if isinstance(val, float):
+            # XXX: add checks for infinity and nan
+            self.write(str(val))
+        else:
+            self.write(repr(val))
+
+    def binop(operator):
+        def visitor(self, node, frame):
+            self.write('(')
+            self.visit(node.left, frame)
+            self.write(' %s ' % operator)
+            self.visit(node.right, frame)
+            self.write(')')
+        return visitor
+
+    def uaop(operator):
+        def visitor(self, node, frame):
+            self.write('(' + operator)
+            self.visit(node.node)
+            self.write(')')
+        return visitor
+
+    visit_Add = binop('+')
+    visit_Sub = binop('-')
+    visit_Mul = binop('*')
+    visit_Div = binop('/')
+    visit_FloorDiv = binop('//')
+    visit_Pow = binop('**')
+    visit_Mod = binop('%')
+    visit_And = binop('and')
+    visit_Or = binop('or')
+    visit_Pos = uaop('+')
+    visit_Neg = uaop('-')
+    visit_Not = uaop('not ')
+    del binop, uaop
+
+    def visit_Compare(self, node, frame):
+        self.visit(node.expr, frame)
+        for op in node.ops:
+            self.visit(op, frame)
+
+    def visit_Operand(self, node, frame):
+        self.write(' %s ' % operators[node.op])
+        self.visit(node.expr, frame)
+
+    def visit_Subscript(self, node, frame):
+        self.write('subscript(')
+        self.visit(node.node, frame)
+        self.write(', ')
+        self.visit(node.arg, frame)
+        self.write(')')