Refactor renderering
diff --git a/lib/mustache.dart b/lib/mustache.dart
index eab0f6c..197b89a 100644
--- a/lib/mustache.dart
+++ b/lib/mustache.dart
@@ -6,7 +6,7 @@
 part 'src/lambda_context.dart';
 part 'src/node.dart';
 part 'src/parse.dart';
-part 'src/renderer.dart';
+part 'src/render_context.dart';
 part 'src/scanner.dart';
 part 'src/template.dart';
 part 'src/token.dart';
diff --git a/lib/src/lambda_context.dart b/lib/src/lambda_context.dart
index ee5f2f8..2ed1e47 100644
--- a/lib/src/lambda_context.dart
+++ b/lib/src/lambda_context.dart
@@ -4,28 +4,37 @@
 class _LambdaContext implements LambdaContext {
   
   final _Node _node;
-  final _Renderer _renderer;
+  final _RenderContext _context;
   final bool _isSection;
   bool _closed = false;
   
-  _LambdaContext(this._node, this._renderer, {bool isSection: true})
+  _LambdaContext(this._node, this._context, {bool isSection: true})
       : _isSection = isSection;
   
   void close() {
     _closed = true;
   }
   
-  _checkClosed() {
-    if (_closed) throw new _TemplateException(
-        'LambdaContext accessed outside of callback.', 
-        _renderer._templateName, _renderer._source, _node.start);
+  void _checkClosed() {
+    if (_closed) throw _error('LambdaContext accessed outside of callback.');
+  }
+  
+  _TemplateException _error(String msg) {
+    return new _TemplateException(msg, _context.templateName, _context.source,
+        _node.start);    
   }
   
   /// Render the current section tag in the current context and return the
   /// result as a string.
   String renderString() {
+    if (_node is! _SectionNode) _error(
+        'LambdaContext.renderString() can only be called on section tags.');
     _checkClosed();
-    return _renderer._renderSubtree(_node);
+    var sink = new StringBuffer();
+    var ctx = new _RenderContext.subtree(_context, sink);
+    _SectionNode section = _node;
+    _renderWithContext(ctx, section.children);
+    return sink.toString();
   }
 
   //FIXME Currently only return values are supported.
@@ -53,7 +62,7 @@
     if (nodes.length == 1 && nodes.first is _TextNode)
       return nodes.first.text;
     
-    var source = _renderer._source.substring(
+    var source = _context.source.substring(
         _node.contentStart, _node.contentEnd);
     
     return source;
@@ -63,27 +72,35 @@
   String renderSource(String source) {
     _checkClosed();
     var sink = new StringBuffer();
+    
     // Lambdas used for sections should parse with the current delimiters.
-    var delimiters = _isSection ? _renderer._delimiters : '{{ }}';
-    var node = _parse(source,
-        _renderer._lenient,
-        _renderer._templateName,
+    var delimiters = '{{ }}';
+    if (_node is _SectionNode) {
+      _SectionNode node = _node;
+      delimiters = node.delimiters;
+    }
+    
+    var nodes = _parse(source,
+        _context.lenient,
+        _context.templateName,
         delimiters);
-    var renderer = new _Renderer.lambda(
-        _renderer,
-        node,
+    
+    var ctx = new _RenderContext.lambda(
+        _context,
         source,
-        _renderer._indent,
+        _context.indent,
         sink,
-        _renderer._delimiters);
-    renderer.render();
+        delimiters);
+    
+    _renderWithContext(ctx, nodes);
+
     return sink.toString();
   }
 
   /// Lookup the value of a variable in the current context.
   Object lookup(String variableName) {
     _checkClosed();
-    return _renderer._resolveValue(variableName);
+    return _context.resolveValue(variableName);
   }
 
 }
\ No newline at end of file
diff --git a/lib/src/node.dart b/lib/src/node.dart
index 1366fd0..edd7b4e 100644
--- a/lib/src/node.dart
+++ b/lib/src/node.dart
@@ -1,10 +1,33 @@
 part of mustache;
 
+void _renderWithContext(_RenderContext ctx, List<_Node> nodes) {
+  if (ctx.indent == null || ctx.indent == '') {
+   nodes.forEach((n) => n.render(ctx));
+
+  } else if (nodes.isNotEmpty) {
+    // Special case to make sure there is not an extra indent after the last
+    // line in the partial file.
+    
+    ctx.write(ctx.indent);
+    
+    for (var n in nodes.take(nodes.length - 1)) {
+      n.render(ctx);
+    }
+        
+    var node = nodes.last;
+    if (node is _TextNode) {
+      node.render(ctx, lastNode: true);
+    } else {
+      node.render(ctx);
+    }
+  }
+}
+
 abstract class _Node {
   
   _Node(this.start, this.end);
   
-  void render(_Renderer renderer);
+  void render(_RenderContext renderer);
   
   // The offset of the start of the token in the file. Unless this is a section
   // or inverse section, then this stores the start of the content of the
@@ -24,16 +47,17 @@
   
   final String text;
   
-  render(_Renderer renderer, {lastNode: false}) {
+  void render(_RenderContext ctx, {lastNode: false}) {
     if (text == '') return;
-    if (renderer._indent == null || renderer._indent == '') {
-      renderer._write(text);
+    if (ctx.indent == null || ctx.indent == '') {
+      ctx.write(text);
     } else if (lastNode && text.runes.last == _NEWLINE) {
+      // Don't indent after the last line in a template.
       var s = text.substring(0, text.length - 1);
-      renderer._write(s.replaceAll('\n', '\n${renderer._indent}'));
-      renderer._write('\n');
+      ctx.write(s.replaceAll('\n', '\n${ctx.indent}'));
+      ctx.write('\n');
     } else {
-      renderer._write(text.replaceAll('\n', '\n${renderer._indent}'));
+      ctx.write(text.replaceAll('\n', '\n${ctx.indent}'));
     }
   }
 }
@@ -46,27 +70,58 @@
   final String name;
   final bool escape;
   
-  render(_Renderer renderer) {
+  void render(_RenderContext ctx) {
     
-    var value = renderer._resolveValue(name);
+    var value = ctx.resolveValue(name);
     
     if (value is Function) {
-      var context = new _LambdaContext(this, renderer, isSection: false);
+      var context = new _LambdaContext(this, ctx, isSection: false);
       value = value(context);
       context.close();
     }
     
     if (value == _noSuchProperty) {
-      if (!renderer._lenient) 
-        throw renderer._error('Value was missing for variable tag: ${name}.', this);
+      if (!ctx.lenient) 
+        throw ctx.error('Value was missing for variable tag: ${name}.', this);
     } else {
       var valueString = (value == null) ? '' : value.toString();
-      var output = !escape || !renderer._htmlEscapeValues
+      var output = !escape || !ctx.htmlEscapeValues
         ? valueString
-        : renderer._htmlEscape(valueString);
-      renderer._write(output);
+        : _htmlEscape(valueString);
+      ctx.write(output);
     }
   }
+  
+  static const Map<String,String> _htmlEscapeMap = const {
+    _AMP: '&amp;',
+    _LT: '&lt;',
+    _GT: '&gt;',
+    _QUOTE: '&quot;',
+    _APOS: '&#x27;',
+    _FORWARD_SLASH: '&#x2F;' 
+  };
+  
+  String _htmlEscape(String s) {
+    
+    var buffer = new StringBuffer();
+    int startIndex = 0;
+    int i = 0;
+    for (int c in s.runes) {
+      if (c == _AMP
+          || c == _LT
+          || c == _GT
+          || c == _QUOTE
+          || c == _APOS
+          || c == _FORWARD_SLASH) {
+        buffer.write(s.substring(startIndex, i));
+        buffer.write(_htmlEscapeMap[c]);
+        startIndex = i + 1;
+      }
+      i++;
+    }
+    buffer.write(s.substring(startIndex));
+    return buffer.toString();
+  }
 }
 
 
@@ -84,64 +139,62 @@
   final List<_Node> children = <_Node>[];
   
   //TODO can probably combine Inv and Normal to shorten.
-  void render(_Renderer renderer) {
-    // Keep track of delimiters for use in LambdaContext.renderSource().
-    renderer._delimiters = delimiters;
-    return inverse ? renderInv(renderer) : renderNormal(renderer);
-  }
+  void render(_RenderContext ctx) => inverse
+      ? renderInv(ctx)
+      : renderNormal(ctx);
   
-  void renderNormal(_Renderer renderer) {
-    var value = renderer._resolveValue(name);
+  void renderNormal(_RenderContext renderer) {
+    var value = renderer.resolveValue(name);
     
     if (value == null) {
       // Do nothing.
     
     } else if (value is Iterable) {
-      value.forEach((v) => renderer._renderSectionWithValue(this, v)); //FIXME probably pull this code into the node?
+      value.forEach((v) => _renderWithValue(renderer, v));
     
     } else if (value is Map) {
-      renderer._renderSectionWithValue(this, value);
+      _renderWithValue(renderer, value);
     
     } else if (value == true) {
-      renderer._renderSectionWithValue(this, value);
+      _renderWithValue(renderer, value);
     
     } else if (value == false) {
       // Do nothing.
     
     } else if (value == _noSuchProperty) {
-      if (!renderer._lenient)
-        throw renderer._error('Value was missing for section tag: ${name}.', this);
+      if (!renderer.lenient)
+        throw renderer.error('Value was missing for section tag: ${name}.', this);
     
     } else if (value is Function) {
       var context = new _LambdaContext(this, renderer, isSection: true);
       var output = value(context);
       context.close();        
-      renderer._write(output);
+      renderer.write(output);
       
     } else {
-      throw renderer._error('Invalid value type for section, '
+      throw renderer.error('Invalid value type for section, '
         'section: ${name}, '
         'type: ${value.runtimeType}.', this);
     }
   }
   
-  void renderInv(_Renderer renderer) {
-    var value = renderer._resolveValue(name);
+  void renderInv(_RenderContext ctx) {
+    var value = ctx.resolveValue(name);
     
     if (value == null) {
-      renderer._renderSectionWithValue(this, null);
+      _renderWithValue(ctx, null);
     
     } else if ((value is Iterable && value.isEmpty) || value == false) {
-      renderer._renderSectionWithValue(this, name);
+      _renderWithValue(ctx, name);
     
     } else if (value == true || value is Map || value is Iterable) {
       // Do nothing.
     
     } else if (value == _noSuchProperty) {
-      if (renderer._lenient) {
-        renderer._renderSectionWithValue(this, null);
+      if (ctx.lenient) {
+        _renderWithValue(ctx, null);
       } else {
-        throw renderer._error('Value was missing for inverse section: ${name}.', this);
+        throw ctx.error('Value was missing for inverse section: ${name}.', this);
       }
   
      } else if (value is Function) {       
@@ -149,12 +202,18 @@
        //TODO in strict mode should this be an error?
   
     } else {
-      throw renderer._error(
+      throw ctx.error(
         'Invalid value type for inverse section, '
         'section: $name, '
         'type: ${value.runtimeType}.', this);
     }
   }
+  
+  void _renderWithValue(_RenderContext ctx, value) {
+    ctx.pushValue(value);
+    children.forEach((n) => n.render(ctx));
+    ctx.popValue();
+  }
 }
 
 class _PartialNode extends _Node {
@@ -168,19 +227,18 @@
   // it's content can be correctly indented.
   final String indent;
   
-  void render(_Renderer renderer) {
+  void render(_RenderContext ctx) {
     var partialName = name;
-    _Template template = renderer._partialResolver == null
+    _Template template = ctx.partialResolver == null
         ? null
-        : renderer._partialResolver(partialName);
+        : ctx.partialResolver(partialName);
     if (template != null) {
-      var r = new _Renderer.partial(renderer, template, this.indent);
-      r.render();
-    } else if (renderer._lenient) {
+      var partialCtx = new _RenderContext.partial(ctx, template, this.indent);
+      _renderWithContext(partialCtx, template._nodes);
+    } else if (ctx.lenient) {
       // do nothing
     } else {
-      throw renderer._error('Partial not found: $partialName.', this);
+      throw ctx.error('Partial not found: $partialName.', this);
     }
   }
 }
-
diff --git a/lib/src/parse.dart b/lib/src/parse.dart
index cbaec3e..fe7f870 100644
--- a/lib/src/parse.dart
+++ b/lib/src/parse.dart
@@ -1,6 +1,6 @@
 part of mustache;
 
-_Node _parse(String source,
+List<_Node> _parse(String source,
              bool lenient,
              String templateName,
              String delimiters) {
@@ -73,7 +73,10 @@
     }
   }
 
-  return stack.last;
+  //FIXME assert stack has only one item and error message if not.
+  // Add test for this.
+  
+  return stack.last.children;
 }
 
 // Takes a list of tokens, and removes _NEWLINE, and _WHITESPACE tokens.
diff --git a/lib/src/render_context.dart b/lib/src/render_context.dart
new file mode 100644
index 0000000..4f1c8f7
--- /dev/null
+++ b/lib/src/render_context.dart
@@ -0,0 +1,126 @@
+part of mustache;
+
+final RegExp _validTag = new RegExp(r'^[0-9a-zA-Z\_\-\.]+$');
+final RegExp _integerTag = new RegExp(r'^[0-9]+$');
+
+const Object _noSuchProperty = const Object();
+
+class _RenderContext {
+  
+  _RenderContext(this._sink,
+      List stack,
+      this.lenient,
+      this.htmlEscapeValues,
+      this.partialResolver,
+      this.templateName,
+      this.indent,
+      this.source)
+    : _stack = new List.from(stack); 
+  
+  _RenderContext.partial(_RenderContext ctx, _Template partial, String indent)
+      : this(ctx._sink,
+          ctx._stack,
+          ctx.lenient,
+          ctx.htmlEscapeValues,
+          ctx.partialResolver,
+          ctx.templateName,
+          ctx.indent + indent,
+          partial.source);
+
+  _RenderContext.subtree(_RenderContext ctx, StringSink sink)
+     : this(sink,
+         ctx._stack,
+         ctx.lenient,
+         ctx.htmlEscapeValues,
+         ctx.partialResolver,
+         ctx.templateName,
+         ctx.indent,
+         ctx.source);
+
+    _RenderContext.lambda(
+        _RenderContext ctx,
+        String source,
+        String indent,
+        StringSink sink,
+        String delimiters)
+       : this(sink,
+           ctx._stack,
+           ctx.lenient,
+           ctx.htmlEscapeValues,
+           ctx.partialResolver,
+           ctx.templateName,
+           ctx.indent + indent,
+           source);
+   
+  final StringSink _sink;
+  final List _stack;
+  final bool lenient;
+  final bool htmlEscapeValues;
+  final PartialResolver partialResolver;
+  final String templateName;
+  final String indent;
+  final String source;
+
+  void pushValue(value) => _stack.add(value);
+  
+  Object popValue() => _stack.removeLast();
+  
+  write(Object output) => _sink.write(output.toString());
+    
+  // Walks up the stack looking for the variable.
+  // Handles dotted names of the form "a.b.c".
+  Object resolveValue(String name) {
+    if (name == '.') {
+      return _stack.last;
+    }
+    var parts = name.split('.');
+    var object = _noSuchProperty;
+    for (var o in _stack.reversed) {
+      object = _getNamedProperty(o, parts[0]);
+      if (object != _noSuchProperty) {
+        break;
+      }
+    }
+    for (int i = 1; i < parts.length; i++) {
+      if (object == null || object == _noSuchProperty) {
+        return _noSuchProperty;
+      }
+      object = _getNamedProperty(object, parts[i]);
+    }
+    return object;
+  }
+  
+  // Returns the property of the given object by name. For a map,
+  // which contains the key name, this is object[name]. For other
+  // objects, this is object.name or object.name(). If no property
+  // by the given name exists, this method returns noSuchProperty.
+  _getNamedProperty(object, name) {
+    
+    if (object is Map && object.containsKey(name))
+      return object[name];
+    
+    if (object is List && _integerTag.hasMatch(name))
+      return object[int.parse(name)];
+    
+    if (lenient && !_validTag.hasMatch(name))
+      return _noSuchProperty;
+    
+    var instance = reflect(object);
+    var field = instance.type.instanceMembers[new Symbol(name)];
+    if (field == null) return _noSuchProperty;
+    
+    var invocation = null;
+    if ((field is VariableMirror) || ((field is MethodMirror) && (field.isGetter))) {
+      invocation = instance.getField(field.simpleName);
+    } else if ((field is MethodMirror) && (field.parameters.length == 0)) {
+      invocation = instance.invoke(field.simpleName, []);
+    }
+    if (invocation == null) {
+      return _noSuchProperty;
+    }
+    return invocation.reflectee;
+  }
+  
+  TemplateException error(String message, _Node node)
+    => new _TemplateException(message, templateName, source, node.start);
+}
diff --git a/lib/src/renderer.dart b/lib/src/renderer.dart
deleted file mode 100644
index f429bf7..0000000
--- a/lib/src/renderer.dart
+++ /dev/null
@@ -1,213 +0,0 @@
-part of mustache;
-
-final RegExp _validTag = new RegExp(r'^[0-9a-zA-Z\_\-\.]+$');
-final RegExp _integerTag = new RegExp(r'^[0-9]+$');
-
-const Object _noSuchProperty = const Object();
-
-class _Renderer {
-  
-  _Renderer(this._root,
-      this._sink,
-      this._values,
-      List stack,
-      this._lenient,
-      this._htmlEscapeValues,
-      this._partialResolver,
-      this._templateName,
-      this._indent,
-      this._source,
-      this._delimiters)
-    : _stack = new List.from(stack); 
-  
-  _Renderer.partial(_Renderer renderer, _Template partial, String indent)
-      : this(partial._root,
-          renderer._sink,
-          renderer._values,
-          renderer._stack,
-          renderer._lenient,
-          renderer._htmlEscapeValues,
-          renderer._partialResolver,
-          renderer._templateName,
-          renderer._indent + indent,
-          partial.source,
-          '{{ }}');
-
-   _Renderer.subtree(_Renderer renderer, _Node node, StringSink sink)
-       : this(node,
-           sink,
-           renderer._values,
-           renderer._stack,
-           renderer._lenient,
-           renderer._htmlEscapeValues,
-           renderer._partialResolver,
-           renderer._templateName,
-           renderer._indent,
-           renderer._source,
-           '{{ }}');
-
-    _Renderer.lambda(
-        _Renderer renderer,
-        _Node node,
-        String source,
-        String indent,
-        StringSink sink,
-        String delimiters)
-       : this(node,
-           sink,
-           renderer._values,
-           renderer._stack,
-           renderer._lenient,
-           renderer._htmlEscapeValues,
-           renderer._partialResolver,
-           renderer._templateName,
-           renderer._indent + indent,
-           source,
-           delimiters);
-   
-  final _SectionNode _root;
-  final StringSink _sink;
-  final _values;
-  final List _stack;
-  final bool _lenient;
-  final bool _htmlEscapeValues;
-  final PartialResolver _partialResolver;
-  final String _templateName;
-  final String _indent;
-  final String _source;
-  
-  // Need to keep track of the current delimiters during rendering.
-  // These are used in LambdaContext.renderSource().
-  String _delimiters;
-  
-  void render() {
-    if (_indent == null || _indent == '') {
-     _root.children.forEach((n) => n.render(this));
-    } else {
-      _renderWithIndent();
-    }
-  }
-  
-  void _renderWithIndent() {
-    // Special case to make sure there is not an extra indent after the last
-    // line in the partial file.
-    var nodes = _root.children; 
-    if (nodes.isEmpty) return;
-    
-    _write(_indent);
-    
-    for (var n in nodes.take(nodes.length - 1)) {
-      n.render(this);
-    }
-        
-    var node = _root.children.last;
-    if (node is _TextNode) {
-      node.render(this, lastNode: true);
-    } else {
-      node.render(this);
-    }
-  }
-  
-  _write(Object output) => _sink.write(output.toString());
-
-  // Walks up the stack looking for the variable.
-  // Handles dotted names of the form "a.b.c".
-  _resolveValue(String name) {
-    if (name == '.') {
-      return _stack.last;
-    }
-    var parts = name.split('.');
-    var object = _noSuchProperty;
-    for (var o in _stack.reversed) {
-      object = _getNamedProperty(o, parts[0]);
-      if (object != _noSuchProperty) {
-        break;
-      }
-    }
-    for (int i = 1; i < parts.length; i++) {
-      if (object == null || object == _noSuchProperty) {
-        return _noSuchProperty;
-      }
-      object = _getNamedProperty(object, parts[i]);
-    }
-    return object;
-  }
-  
-  // Returns the property of the given object by name. For a map,
-  // which contains the key name, this is object[name]. For other
-  // objects, this is object.name or object.name(). If no property
-  // by the given name exists, this method returns noSuchProperty.
-  _getNamedProperty(object, name) {
-    
-    if (object is Map && object.containsKey(name))
-      return object[name];
-    
-    if (object is List && _integerTag.hasMatch(name))
-      return object[int.parse(name)];
-    
-    if (_lenient && !_validTag.hasMatch(name))
-      return _noSuchProperty;
-    
-    var instance = reflect(object);
-    var field = instance.type.instanceMembers[new Symbol(name)];
-    if (field == null) return _noSuchProperty;
-    
-    var invocation = null;
-    if ((field is VariableMirror) || ((field is MethodMirror) && (field.isGetter))) {
-      invocation = instance.getField(field.simpleName);
-    } else if ((field is MethodMirror) && (field.parameters.length == 0)) {
-      invocation = instance.invoke(field.simpleName, []);
-    }
-    if (invocation == null) {
-      return _noSuchProperty;
-    }
-    return invocation.reflectee;
-  }
-
-  void _renderSectionWithValue(node, value) {
-    _stack.add(value);
-    node.children.forEach((n) => n.render(this));
-    _stack.removeLast();
-  }
-
-  String _renderSubtree(node) {
-    var sink = new StringBuffer();
-    var renderer = new _Renderer.subtree(this, node, sink);
-    renderer.render();
-    return sink.toString();
-  }
-
-  static const Map<String,String> _htmlEscapeMap = const {
-    _AMP: '&amp;',
-    _LT: '&lt;',
-    _GT: '&gt;',
-    _QUOTE: '&quot;',
-    _APOS: '&#x27;',
-    _FORWARD_SLASH: '&#x2F;' 
-  };
-  
-  String _htmlEscape(String s) {
-    
-    var buffer = new StringBuffer();
-    int startIndex = 0;
-    int i = 0;
-    for (int c in s.runes) {
-      if (c == _AMP
-          || c == _LT
-          || c == _GT
-          || c == _QUOTE
-          || c == _APOS
-          || c == _FORWARD_SLASH) {
-        buffer.write(s.substring(startIndex, i));
-        buffer.write(_htmlEscapeMap[c]);
-        startIndex = i + 1;
-      }
-      i++;
-    }
-    buffer.write(s.substring(startIndex));
-    return buffer.toString();
-  }
-  
-  TemplateException _error(String message, _Node node)
-    => new _TemplateException(message, _templateName, _source, node.start);
-}
diff --git a/lib/src/template.dart b/lib/src/template.dart
index 6dbe3b1..1e5643b 100644
--- a/lib/src/template.dart
+++ b/lib/src/template.dart
@@ -8,14 +8,14 @@
         String name,

         PartialResolver partialResolver})

        :  source = source,

-          _root = _parse(source, lenient, name, '{{ }}'),

+          _nodes = _parse(source, lenient, name, '{{ }}'),

           _lenient = lenient,

           _htmlEscapeValues = htmlEscapeValues,

           _name = name,

           _partialResolver = partialResolver;

   

   final String source;

-  final _Node _root;

+  final List<_Node> _nodes;

   final bool _lenient;

   final bool _htmlEscapeValues;

   final String _name;

@@ -30,10 +30,9 @@
   }

 

   void render(values, StringSink sink) {

-    var renderer = new _Renderer(_root, sink, values, [values],

-        _lenient, _htmlEscapeValues, _partialResolver, _name, '', source,

-        '{{ }}');

-    renderer.render();

+    var ctx = new _RenderContext(sink, [values], _lenient, _htmlEscapeValues,

+        _partialResolver, _name, '', source);

+    _renderWithContext(ctx, _nodes);

   }

 }