fixed one bug with blocks, one to go

--HG--
branch : trunk
diff --git a/jinja2/compiler.py b/jinja2/compiler.py
index 44087f1..48c9d99 100644
--- a/jinja2/compiler.py
+++ b/jinja2/compiler.py
@@ -257,11 +257,17 @@
         assert frame is None, 'no root frame allowed'
         self.writeline('from jinja2.runtime import *')
         self.writeline('filename = %r' % self.filename)
-        self.writeline('template_context = TemplateContext(global_context, '
-                       'filename)')
+
+        # find all blocks
+        for block in node.find_all(nodes.Block):
+            if block.name in self.blocks:
+                raise TemplateAssertionError('block %r defined twice' %
+                                             block.name, block.lineno,
+                                             self.filename)
+            self.blocks[block.name] = block
 
         # generate the root render function.
-        self.writeline('def root(context=template_context):', extra=1)
+        self.writeline('def root(context):', extra=1)
         self.indent()
         self.writeline('parent_root = None')
         self.outdent()
@@ -285,17 +291,13 @@
             block_frame = Frame()
             block_frame.inspect(block.body)
             block_frame.block = name
+            print block_frame.identifiers.__dict__
             self.writeline('def block_%s(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(context):' % node.name)
         self.indent()
         self.writeline('yield event')
@@ -429,6 +431,11 @@
     def visit_Output(self, node, frame):
         self.newline(node)
 
+        if self.environment.finalize is unicode:
+            finalizer = 'unicode'
+        else:
+            finalizer = 'context.finalize'
+
         # try to evaluate as many chunks as possible into a static
         # string at compile time.
         body = []
@@ -450,7 +457,7 @@
                     self.writeline('yield %s' % repr(u''.join(item)))
                 else:
                     self.newline(item)
-                    self.write('yield unicode(')
+                    self.write('yield %s(' % finalizer)
                     self.visit(item, frame)
                     self.write(')')
 
@@ -469,7 +476,11 @@
             for idx, argument in enumerate(arguments):
                 if idx:
                     self.write(', ')
+                if finalizer != 'unicode':
+                    self.write('(')
                 self.visit(argument, frame)
+                if finalizer != 'unicode':
+                    self.write(')')
             self.write(idx == 0 and ',)' or ')')
 
     def visit_Assign(self, node, frame):
@@ -514,6 +525,24 @@
             self.visit(item, frame)
         self.write(idx == 0 and ',)' or ')')
 
+    def visit_List(self, node, frame):
+        self.write('[')
+        for idx, item in enumerate(node.items):
+            if idx:
+                self.write(', ')
+            self.visit(item, frame)
+        self.write(']')
+
+    def visit_Dict(self, node, frame):
+        self.write('{')
+        for idx, item in enumerate(node.items):
+            if idx:
+                self.write(', ')
+            self.visit(item.key, frame)
+            self.write(': ')
+            self.visit(item.value, frame)
+        self.write('}')
+
     def binop(operator):
         def visitor(self, node, frame):
             self.write('(')
diff --git a/jinja2/environment.py b/jinja2/environment.py
index 77e0d03..8ba4fce 100644
--- a/jinja2/environment.py
+++ b/jinja2/environment.py
@@ -10,13 +10,11 @@
 """
 from jinja2.lexer import Lexer
 from jinja2.parser import Parser
-from jinja2.runtime import Undefined
 from jinja2.defaults import DEFAULT_FILTERS, DEFAULT_TESTS, DEFAULT_NAMESPACE
 
 
 class Environment(object):
-    """
-    The Jinja environment.
+    """The Jinja environment.
 
     The core component of Jinja is the `Environment`. It contains
     important shared variables like configuration, filters, tests,
@@ -32,8 +30,7 @@
                  comment_end_string='#}',
                  trim_blocks=False,
                  template_charset='utf-8'):
-        """
-        Here the possible initialization parameters:
+        """Here the possible initialization parameters:
 
         ========================= ============================================
         `block_start_string`      the string marking the begin of a block.
@@ -68,26 +65,25 @@
         self.tests = DEFAULT_TESTS.copy()
         self.globals = DEFAULT_NAMESPACE.copy()
 
-        # the factory that creates the undefined object
-        self.undefined_factory = Undefined
+        # if no finalize function/method exists we default to unicode.  The
+        # compiler check if the finalize attribute *is* unicode, if yes no
+        # finalizaion is written where it can be avoided.
+        if not hasattr(self, 'finalize'):
+            self.finalize = unicode
 
         # create lexer
         self.lexer = Lexer(self)
 
     def parse(self, source, filename=None):
-        """
-        Parse the sourcecode and return the abstract syntax tree. This tree
-        of nodes is used by the `translators`_ to convert the template into
+        """Parse the sourcecode and return the abstract syntax tree. This tree
+        of nodes is used by the compiler to convert the template into
         executable source- or bytecode.
-
-        .. _translators: translators.txt
         """
         parser = Parser(self, source, filename)
         return parser.parse()
 
     def lex(self, source, filename=None):
-        """
-        Lex the given sourcecode and return a generator that yields tokens.
+        """Lex the given sourcecode and return a generator that yields tokens.
         The stream returned is not usable for Jinja but can be used if
         Jinja templates should be processed by other tools (for example
         syntax highlighting etc)
diff --git a/jinja2/filters.py b/jinja2/filters.py
index 8b66ce8..1c3ffcb 100644
--- a/jinja2/filters.py
+++ b/jinja2/filters.py
@@ -15,15 +15,14 @@
 except ImportError:
     itemgetter = lambda a: lambda b: b[a]
 from urllib import urlencode, quote
-from jinja.utils import escape
+from jinja2.utils import escape
 
 
 _striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
 
 
 def contextfilter(f):
-    """
-    Decorator for marking context dependent filters. The current context
+    """Decorator for marking context dependent filters. The current context
     argument will be passed as first argument.
     """
     f.contextfilter = True
@@ -60,17 +59,13 @@
 
 
 def do_upper(s):
-    """
-    Convert a value to uppercase.
-    """
-    return s.upper()
+    """Convert a value to uppercase."""
+    return unicode(s).upper()
 
 
 def do_lower(s):
-    """
-    Convert a value to lowercase.
-    """
-    return s.lower()
+    """Convert a value to lowercase."""
+    return unicode(s).lower()
 
 
 def do_escape(s, attribute=False):
@@ -82,11 +77,6 @@
 
     This method will have no effect it the value is already escaped.
     """
-    # XXX: Does this still exists?
-    #if isinstance(s, TemplateData):
-    #    return s
-    if hasattr(s, '__html__'):
-        return s.__html__()
     return escape(unicode(s), attribute)
 
 
diff --git a/jinja2/optimizer.py b/jinja2/optimizer.py
index e16961f..167f6eb 100644
--- a/jinja2/optimizer.py
+++ b/jinja2/optimizer.py
@@ -26,6 +26,7 @@
 
 class ContextStack(object):
     """Simple compile time context implementation."""
+    undefined = object()
 
     def __init__(self, initial=None):
         self.stack = [{}]
@@ -44,10 +45,24 @@
         except KeyError:
             return default
 
+    def undef(self, name):
+        if name in self:
+            self[name] = self.undefined
+
+    def __contains__(self, key):
+        try:
+            self[key]
+        except KeyError:
+            return False
+        return True
+
     def __getitem__(self, key):
         for level in reversed(self.stack):
             if key in level:
-                return level[key]
+                rv = level[key]
+                if rv is self.undefined:
+                    raise KeyError(key)
+                return rv
         raise KeyError(key)
 
     def __setitem__(self, key, value):
@@ -66,6 +81,13 @@
     def visit_Block(self, node, context):
         return self.generic_visit(node, context.blank())
 
+    def visit_Macro(self, node, context):
+        context.push()
+        try:
+            return self.generic_visit(node, context)
+        finally:
+            context.pop()
+
     def visit_For(self, node, context):
         """Loop unrolling for iterable constant values."""
         try:
@@ -73,6 +95,9 @@
             # we only unroll them if they have a length and are iterable
             iter(iterable)
             len(iterable)
+            # we also don't want unrolling if macros are defined in it
+            if node.find(nodes.Macro) is not None:
+                raise TypeError()
         except (nodes.Impossible, TypeError):
             return self.generic_visit(node, context)
 
@@ -124,6 +149,9 @@
 
     def visit_Name(self, node, context):
         if node.ctx != 'load':
+            # something overwrote the variable, we can no longer use
+            # the constant from the context
+            context.undef(node.name)
             return node
         try:
             return nodes.Const.from_untrusted(context[node.name],
diff --git a/jinja2/parser.py b/jinja2/parser.py
index a7f0e98..74cc421 100644
--- a/jinja2/parser.py
+++ b/jinja2/parser.py
@@ -55,7 +55,6 @@
         if token_type in _statement_keywords:
             return getattr(self, 'parse_' + token_type)()
         elif token_type is 'call':
-            self.stream.next()
             return self.parse_call_block()
         lineno = self.stream.current
         expr = self.parse_tuple()
@@ -179,7 +178,10 @@
 
     def parse_call_block(self):
         node = nodes.CallBlock(lineno=self.stream.expect('call').lineno)
-        node.call = self.parse_call()
+        node.call = self.parse_expression()
+        if not isinstance(node.call, nodes.Call):
+            raise TemplateSyntaxError('expected call', node.lineno,
+                                      self.filename)
         node.body = self.parse_statements(('endcall',), drop_needle=True)
         return node
 
diff --git a/jinja2/runtime.py b/jinja2/runtime.py
index 5101d0f..b80a488 100644
--- a/jinja2/runtime.py
+++ b/jinja2/runtime.py
@@ -33,9 +33,17 @@
             return Undefined(obj, argument)
 
 
+class TemplateData(unicode):
+    """Marks data as "coming from the template".  This is used to let the
+    system know that this data is already processed if a finalization is
+    used."""
+
+    def __html__(self):
+        return self
+
+
 class TemplateContext(dict):
-    """
-    Holds the variables of the local template or of the global one.  It's
+    """Holds the variables of the local template or of the global one.  It's
     not save to use this class outside of the compiled code.  For example
     update and other methods will not work as they seem (they don't update
     the exported variables for example).
@@ -177,7 +185,7 @@
             arguments['l_' + name] = value
         if self.catch_all:
             arguments['l_arguments'] = kwargs
-        return u''.join(self.func(**arguments))
+        return TemplateData(u''.join(self.func(**arguments)))
 
 
 class Undefined(object):
diff --git a/jinja2/utils.py b/jinja2/utils.py
index 3f6395e..23a3b15 100644
--- a/jinja2/utils.py
+++ b/jinja2/utils.py
@@ -8,3 +8,16 @@
     :copyright: 2008 by Armin Ronacher.
     :license: BSD, see LICENSE for more details.
 """
+
+
+def escape(obj, attribute=False):
+    """HTML escape an object."""
+    if hasattr(obj, '__html__'):
+        return obj.__html__()
+    s = unicode(obj) \
+        .replace('&', '&amp;') \
+        .replace('>', '&gt;') \
+        .replace('<', '&lt;')
+    if attribute:
+        s = s.replace('"', '&quot;')
+    return s
diff --git a/test.py b/test.py
index 7ab5b96..0629c14 100644
--- a/test.py
+++ b/test.py
@@ -5,24 +5,17 @@
 
 env = Environment()
 ast = env.parse("""
-{% block body %}
-    {% b = 23 %}
-    {% macro foo(a) %}[{{ a }}|{{ b }}|{{ c }}]{% endmacro %}
-    {% for item in seq %}
-      {{ foo(item) }}
-    {%- endfor %}
+hallo
+{% block root %}
+  inhalt
+  {% x = 3 %}
+  {% block inner %}
+    {% x = x + 2 %}
+    {{ x }}
+  {% endblock %}
+  {{ x }}
 {% endblock %}
+ende
 """)
-print ast
-print
 source = generate(ast, env, "foo.html")
 print source
-print
-
-# execute the template
-code = compile(source, 'jinja://foo.html', 'exec')
-context = {'seq': range(5), 'c': 'foobar'}
-namespace = {'global_context': context}
-exec code in namespace
-for event in namespace['root'](context):
-    sys.stdout.write(event)