Combine RenderContext and Renderer
diff --git a/lib/src/lambda_context.dart b/lib/src/lambda_context.dart
index 261e64c..130e445 100644
--- a/lib/src/lambda_context.dart
+++ b/lib/src/lambda_context.dart
@@ -4,7 +4,6 @@
 
 import 'node.dart';
 import 'parser.dart' as parser;
-import 'render_context.dart';
 import 'renderer.dart';
 import 'template_exception.dart';
 
@@ -12,11 +11,11 @@
 class LambdaContext implements m.LambdaContext {
   
   final Node _node;
-  final RenderContext _context;
+  final Renderer _renderer;
   final bool _isSection;
   bool _closed = false;
   
-  LambdaContext(this._node, this._context, {bool isSection: true})
+  LambdaContext(this._node, this._renderer, {bool isSection: true})
       : _isSection = isSection;
   
   void close() {
@@ -28,7 +27,7 @@
   }
   
   TemplateException _error(String msg) {
-    return new TemplateException(msg, _context.templateName, _context.source,
+    return new TemplateException(msg, _renderer.templateName, _renderer.source,
         _node.start);    
   }
   
@@ -44,10 +43,9 @@
   }
 
   void _renderSubtree(StringSink sink, Object value) {
-    var ctx = new RenderContext.subtree(_context, sink);
-    var renderer = new Renderer(ctx);
+    var renderer = new Renderer.subtree(_renderer, sink);
     SectionNode section = _node;
-    if (value != null) ctx.push(value);
+    if (value != null) renderer.push(value);
     renderer.render(section.children);
   }
   
@@ -55,12 +53,12 @@
     _checkClosed();
     if (_node is! SectionNode) _error(
         'LambdaContext.render() can only be called on section tags.');
-    _renderSubtree(_context.sink, value);
+    _renderSubtree(_renderer.sink, value);
   }
 
   void write(Object object) {
     _checkClosed();
-    _context.write(object);
+    _renderer.write(object);
   }
   
   /// Get the unevaluated template source for the current section tag.
@@ -77,7 +75,7 @@
     
     if (nodes.length == 1 && nodes.first is TextNode) return nodes.first.text;
     
-    return _context.source.substring(node.contentStart, node.contentEnd);
+    return _renderer.source.substring(node.contentStart, node.contentEnd);
   }
 
   /// Evaluate the string as a mustache template using the current context.
@@ -93,20 +91,18 @@
     }
     
     var nodes = parser.parse(source,
-        _context.lenient,
-        _context.templateName,
+        _renderer.lenient,
+        _renderer.templateName,
         delimiters);
     
-    var ctx = new RenderContext.lambda(
-        _context,
+    var renderer = new Renderer.lambda(
+        _renderer,
         source,
-        _context.indent,
+        _renderer.indent,
         sink,
         delimiters);
     
-    var renderer = new Renderer(ctx);
-    
-    if (value != null) ctx.push(value);
+    if (value != null) renderer.push(value);
     renderer.render(nodes);
 
     return sink.toString();
@@ -115,7 +111,7 @@
   /// Lookup the value of a variable in the current context.
   Object lookup(String variableName) {
     _checkClosed();
-    return _context.resolveValue(variableName);
+    return _renderer.resolveValue(variableName);
   }
 
 }
\ No newline at end of file
diff --git a/lib/src/renderer.dart b/lib/src/renderer.dart
index d3fc0e8..dcc0a96 100644
--- a/lib/src/renderer.dart
+++ b/lib/src/renderer.dart
@@ -1,31 +1,91 @@
 library mustache.renderer;
 
+@MirrorsUsed(metaTargets: const [m.mustache])
+import 'dart:mirrors';
+import 'package:mustache/mustache.dart' as m;
 import 'lambda_context.dart';
 import 'node.dart';
-import 'render_context.dart';
 import 'template.dart';
+import 'template_exception.dart';
+
+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 extends Visitor {
+    
+  Renderer(this.sink,
+      List stack,
+      this.lenient,
+      this.htmlEscapeValues,
+      this.partialResolver,
+      this.templateName,
+      this.indent,
+      this.source)
+    : _stack = new List.from(stack); 
   
-  Renderer(this.ctx);
-  
-  //TODO merge classes together.
-  RenderContext ctx;
+  Renderer.partial(Renderer ctx, Template partial, String indent)
+      : this(ctx.sink,
+          ctx._stack,
+          ctx.lenient,
+          ctx.htmlEscapeValues,
+          ctx.partialResolver,
+          ctx.templateName,
+          ctx.indent + indent,
+          partial.source);
 
+  Renderer.subtree(Renderer ctx, StringSink sink)
+     : this(sink,
+         ctx._stack,
+         ctx.lenient,
+         ctx.htmlEscapeValues,
+         ctx.partialResolver,
+         ctx.templateName,
+         ctx.indent,
+         ctx.source);
+
+  Renderer.lambda(
+      Renderer 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 m.PartialResolver partialResolver;
+  final String templateName;
+  final String indent;
+  final String source;
+
+  void push(value) => _stack.add(value);
+  
+  Object pop() => _stack.removeLast();
+  
+  write(Object output) => sink.write(output.toString());
+  
   void render(List<Node> nodes) {
 
-    if (ctx.indent == null || ctx.indent == '') {      
+    if (indent == null || indent == '') {      
      nodes.forEach((n) => n.accept(this));
 
     } else if (nodes.isNotEmpty) {
       // Special case to make sure there is not an extra indent after the last
-      // line in the partial file.
+      // line in the partial file.      
+      write(indent);
       
-      ctx.write(ctx.indent);
-      
-      for (var n in nodes.take(nodes.length - 1)) {
-        n.accept(this);
-      }
+      nodes.take(nodes.length - 1).forEach((n) => n.accept(this));
       
       var node = nodes.last;
       if (node is TextNode) {
@@ -39,37 +99,37 @@
   void visitText(TextNode node, {bool lastNode: false}) {
     
     if (node.text == '') return;
-    if (ctx.indent == null || ctx.indent == '') {
-      ctx.write(node.text);
+    if (indent == null || indent == '') {
+      write(node.text);
     } else if (lastNode && node.text.runes.last == _NEWLINE) {
       // Don't indent after the last line in a template.
       var s = node.text.substring(0, node.text.length - 1);
-      ctx.write(s.replaceAll('\n', '\n${ctx.indent}'));
-      ctx.write('\n');
+      write(s.replaceAll('\n', '\n${indent}'));
+      write('\n');
     } else {
-      ctx.write(node.text.replaceAll('\n', '\n${ctx.indent}'));
+      write(node.text.replaceAll('\n', '\n${indent}'));
     }
   }
   
   void visitVariable(VariableNode node) {
-    var value = ctx.resolveValue(node.name);
+    var value = resolveValue(node.name);
     
     if (value is Function) {
-      var context = new LambdaContext(node, ctx, isSection: false);
+      var context = new LambdaContext(node, this, isSection: false);
       value = value(context);
       context.close();
     }
     
     if (value == noSuchProperty) {
-      if (!ctx.lenient) 
-        throw ctx.error('Value was missing for variable tag: ${node.name}.',
+      if (!lenient) 
+        throw error('Value was missing for variable tag: ${node.name}.',
             node);
     } else {
       var valueString = (value == null) ? '' : value.toString();
-      var output = !node.escape || !ctx.htmlEscapeValues
+      var output = !node.escape || !htmlEscapeValues
         ? valueString
         : _htmlEscape(valueString);
-      if (output != null) ctx.write(output);
+      if (output != null) write(output);
     }  
   }
   
@@ -79,7 +139,7 @@
  
   //TODO can probably combine Inv and Normal to shorten.   
    void _renderSection(SectionNode node) {
-     var value = ctx.resolveValue(node.name);
+     var value = resolveValue(node.name);
      
      if (value == null) {
        // Do nothing.
@@ -97,25 +157,24 @@
        // Do nothing.
      
      } else if (value == noSuchProperty) {
-       if (!ctx.lenient)
-         throw ctx.error('Value was missing for section tag: ${node.name}.',
-             node);
+       if (!lenient)
+         throw error('Value was missing for section tag: ${node.name}.', node);
      
      } else if (value is Function) {
-       var context = new LambdaContext(node, ctx, isSection: true);
+       var context = new LambdaContext(node, this, isSection: true);
        var output = value(context);
        context.close();        
-       if (output != null) ctx.write(output);
+       if (output != null) write(output);
        
      } else {
-       throw ctx.error('Invalid value type for section, '
+       throw error('Invalid value type for section, '
          'section: ${node.name}, '
          'type: ${value.runtimeType}.', node);
      }
    }
    
    void _renderInvSection(SectionNode node) {
-     var value = ctx.resolveValue(node.name);
+     var value = resolveValue(node.name);
      
      if (value == null) {
        _renderWithValue(node, null);
@@ -127,10 +186,10 @@
        // Do nothing.
      
      } else if (value == noSuchProperty) {
-       if (ctx.lenient) {
+       if (lenient) {
          _renderWithValue(node, null);
        } else {
-         throw ctx.error('Value was missing for inverse section: ${node.name}.', node);
+         throw error('Value was missing for inverse section: ${node.name}.', node);
        }
    
       } else if (value is Function) {       
@@ -138,7 +197,7 @@
         //TODO in strict mode should this be an error?
    
      } else {
-       throw ctx.error(
+       throw error(
          'Invalid value type for inverse section, '
          'section: ${node.name}, '
          'type: ${value.runtimeType}.', node);
@@ -146,28 +205,84 @@
    }
    
    void _renderWithValue(SectionNode node, value) {
-     ctx.push(value);
+     push(value);
      node.visitChildren(this);
-     ctx.pop();
+     pop();
    }
   
   void visitPartial(PartialNode node) {
     var partialName = node.name;
-    Template template = ctx.partialResolver == null
+    Template template = partialResolver == null
         ? null
-        : ctx.partialResolver(partialName);
+        : partialResolver(partialName);
     if (template != null) {
-      var partialCtx = new RenderContext.partial(ctx, template, node.indent);
-      var renderer = new Renderer(partialCtx);
+      var renderer = new Renderer.partial(this, template, node.indent);
       var nodes = getTemplateNodes(template);
       renderer.render(nodes);
-    } else if (ctx.lenient) {
+    } else if (lenient) {
       // do nothing
     } else {
-      throw ctx.error('Partial not found: $partialName.', node);
+      throw error('Partial not found: $partialName.', node);
     } 
   }
   
+  // 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;
+  }
+  
+  m.TemplateException error(String message, Node node)
+    => new TemplateException(message, templateName, source, node.start);
+
   static const Map<String,String> _htmlEscapeMap = const {
     _AMP: '&amp;',
     _LT: '&lt;',
@@ -198,6 +313,7 @@
     buffer.write(s.substring(startIndex));
     return buffer.toString();
   }
+
 }
 
 const int _AMP = 38;
diff --git a/lib/src/template.dart b/lib/src/template.dart
index 70ec776..8d85f18 100644
--- a/lib/src/template.dart
+++ b/lib/src/template.dart
@@ -37,9 +37,8 @@
   }

 

   void render(values, StringSink sink) {

-    var ctx = new RenderContext(sink, [values], _lenient, _htmlEscapeValues,

+    var renderer = new Renderer(sink, [values], _lenient, _htmlEscapeValues,

         _partialResolver, _name, '', source);

-    var renderer = new Renderer(ctx);

     renderer.render(_nodes);

   }

 }