Add initial parser re-implementation
diff --git a/lib/src/mustache_impl.dart b/lib/src/mustache_impl.dart
index 1531e9d..0c11e5a 100644
--- a/lib/src/mustache_impl.dart
+++ b/lib/src/mustache_impl.dart
@@ -5,6 +5,8 @@
 
 import 'package:mustache/mustache.dart' as m;
 
+import 'parser.dart' as parser;
+
 part 'lambda_context.dart';
 part 'node.dart';
 part 'parse.dart';
diff --git a/lib/src/node.dart b/lib/src/node.dart
index 20b9ece..0d90176 100644
--- a/lib/src/node.dart
+++ b/lib/src/node.dart
@@ -47,6 +47,16 @@
   
   final String text;
   
+  String toString() => '(TextNode "$text" $start $end)';
+  
+  // Only used for testing.
+  bool operator ==(o) => o is TextNode
+      && text == o.text
+      && start == o.start
+      && end == o.end;
+  
+  // TODO hashcode. import quiver.
+  
   void render(RenderContext ctx, {lastNode: false}) {
     if (text == '') return;
     if (ctx.indent == null || ctx.indent == '') {
@@ -64,12 +74,24 @@
 
 class VariableNode extends Node {
   
-  VariableNode(this.name, int start, int end, {this.escape: false})
+  VariableNode(this.name, int start, int end, {this.escape: true})
     : super(start, end);
   
   final String name;
   final bool escape;
   
+  String toString() => '(VariableNode "$name" escape: $escape $start $end)';
+  
+  // Only used for testing.
+  bool operator ==(o) => o is VariableNode
+      && name == o.name
+      && escape == o.escape
+      && start == o.start
+      && end == o.end;
+  
+  // TODO hashcode. import quiver.
+
+  
   void render(RenderContext ctx) {
     
     var value = ctx.resolveValue(name);
@@ -137,6 +159,20 @@
   int contentStart;
   int contentEnd;
   final List<Node> children = <Node>[];
+
+  toString() => '(SectionNode $name inverse: $inverse)';
+  
+  // TODO Only used for testing.
+  //FIXME use deepequals in test for comparing children.
+  //Perhaps shift all of this == code into test.
+  bool operator ==(o) => o is SectionNode
+      && name == o.name
+      && delimiters == o.delimiters
+      && inverse == o.inverse
+      && contentStart == o.contentEnd;
+  
+  // TODO hashcode. import quiver.
+
   
   //TODO can probably combine Inv and Normal to shorten.
   void render(RenderContext ctx) => inverse
@@ -226,6 +262,14 @@
   // Used to store the preceding whitespace before a partial tag, so that
   // it's content can be correctly indented.
   final String indent;
+
+  //TODO move to test.
+  bool operator ==(o) => o is PartialNode
+      && name == o.name
+      && indent == o.indent;
+  
+  // TODO hashcode. import quiver.
+
   
   void render(RenderContext ctx) {
     var partialName = name;
diff --git a/lib/src/parser.dart b/lib/src/parser.dart
new file mode 100644
index 0000000..484e7b1
--- /dev/null
+++ b/lib/src/parser.dart
@@ -0,0 +1,275 @@
+library parser;
+
+//TODO just import nodes.
+import 'mustache_impl.dart' show Node, SectionNode, TextNode, PartialNode, VariableNode;
+import 'scanner2.dart';
+import 'template_exception.dart';
+import 'token2.dart';
+
+List<Node> parse(String source,
+             bool lenient,
+             String templateName,
+             String delimiters) {
+  var parser = new Parser(source, templateName, delimiters, lenient: lenient);
+  return parser.parse();
+}
+
+class Parser {
+  
+  Parser(this._source, this._templateName, this._delimiters, {lenient: false})
+      : _lenient = lenient {
+    // _scanner = new Scanner(_source, _templateName, _delimiters, _lenient);
+  }
+  
+  //TODO do I need to keep all of these variables around?
+  final String _source;
+  final bool _lenient;
+  final String _templateName;
+  final String _delimiters;  
+  Scanner _scanner; //TODO make final
+  List<Token> _tokens;
+  final List<SectionNode> _stack = <SectionNode>[];
+  String _currentDelimiters;
+  
+  int _i = 0;
+  
+  //TODO EOF??
+  Token _peek() => _i < _tokens.length ? _tokens[_i] : null;
+  
+  // TODO EOF?? return null on EOF?
+  Token _read() {
+    var t = null;
+    if (_i < _tokens.length) {
+      t = _tokens[_i];
+      _i++;
+    }
+    return t;
+  }
+  
+  //TODO use a sync* generator once landed in Dart 1.10.
+  Iterable<Token> _readWhile(bool predicate(Token t)) {
+    var list = <Token>[];
+    for (var t = _peek(); t != null && predicate(t); t = _peek()) {
+      _read();
+      list.add(t);
+    }
+    return list;
+  }
+  
+  List<Node> parse() {
+    _scanner = new Scanner(_source, _templateName, _delimiters,
+        lenient: _lenient);
+    
+    _tokens = _scanner.scan();
+    _tokens = _removeStandaloneWhitespace(_tokens);
+    _tokens = _mergeAdjacentText(_tokens);
+    
+    _currentDelimiters = _delimiters;
+    
+    _stack.add(new SectionNode('root', 0, 0, _delimiters));    
+    
+    for (var token = _peek(); token != null; token = _peek()) {
+      
+      if (token.type == TokenType.text) {
+        _read();
+        _stack.last.children.add(
+            new TextNode(token.value, token.start, token.end));
+      
+      } else if (token.type == TokenType.openDelimiter) {
+        if (token.value == '{{{') {
+          _parseTripleMustacheTag();
+        } else {
+          _parseTag();
+        }
+      } else if (token.type == TokenType.changeDelimiter) {
+        _read();
+        _currentDelimiters = token.value;
+      } else {
+        throw 'boom!';
+      }
+    }
+    
+    //TODO proper error message.
+    assert(_stack.length == 1);
+    
+    return _stack.last.children;
+  }
+  
+  void _parseTripleMustacheTag() {
+    var open = _read();
+    var name = _parseIdentifier();
+    var close = _read();
+    _stack.last.children.add(
+      new VariableNode(name, open.start, open.end, escape: false));
+  }
+  
+  void _parseTag() {
+    var open = _read();
+    
+    if (_peek().type == TokenType.whitespace) _read();
+    
+    // sigil character, or null. A sigil is the character which identifies which
+    // sort of tag it is, i.e.  '#', '/', or '>'.
+    var sigil = _peek().type == TokenType.sigil ? _read().value : null;
+    
+    if (_peek().type == TokenType.whitespace) _read();
+    
+    // TODO split up names here instead of during render.
+    // Also check that they are valid token types.
+    var name = _parseIdentifier();
+    
+    var close = _read();
+    
+    if (sigil == '#' || sigil == '^') {
+      // Section and inverser section.
+      bool inverse = sigil == '^';
+      var node = new SectionNode(name, open.start, close.end, 
+          _currentDelimiters, inverse: inverse);
+      _stack.last.children.add(node);
+      _stack.add(node);
+    
+    } else if (sigil == '/') {
+      // Close section tag
+      if (name != _stack.last.name) throw 'boom!';
+      _stack.removeLast();
+    
+    } else if (sigil == '&' || sigil == null) {
+      // Variable and unescaped variable tag
+      bool escape = sigil == null;
+      _stack.last.children.add(
+        new VariableNode(name, open.start, close.end, escape: escape));
+      
+    } else if (sigil == '>') {
+      // Partial tag
+      //TODO find precending whitespace.
+      var indent = '';
+      _stack.last.children.add(
+          new PartialNode(name, open.start, close.end, indent));
+    
+    } else if (sigil == '!') {
+      // Ignore comments
+    
+    } else {
+      assert(false); //TODO  
+    }
+  }
+  
+  //TODO shouldn't just return a string.
+  String _parseIdentifier() {
+    // TODO split up names here instead of during render.
+    // Also check that they are valid token types.
+    var name = _readWhile((t) => t.type != TokenType.closeDelimiter)
+         .map((t) => t.value)
+         .join()
+         .trim();
+    
+    return name;
+  }
+
+  // Takes a list of tokens, and removes _NEWLINE, and _WHITESPACE tokens.
+  // This is used to implement mustache standalone lines.
+  // Where TAG is one of: OPEN_SECTION, INV_SECTION, CLOSE_SECTION
+  // LINE_END, [WHITESPACE], TAG, [WHITESPACE], LINE_END => LINE_END, TAG
+  // WHITESPACE => TEXT
+  // LINE_END => TEXT
+  // TODO could rewrite this to use a generator, rather than creating an inter-
+  // mediate list.
+  List<Token> _removeStandaloneWhitespace(List<Token> tokens) {
+    int i = 0;
+    Token read() { var ret = i < tokens.length ? tokens[i++] : null; return ret; }
+    Token peek([int n = 0]) => i + n < tokens.length ? tokens[i + n] : null;
+    
+    bool isTag(token) => token != null
+       && const [TokenType.openDelimiter, TokenType.changeDelimiter].contains(token.type);
+    
+    bool isWhitespace(token) => token != null && token.type == TokenType.whitespace;
+    bool isLineEnd(token) => token != null && token.type == TokenType.lineEnd;
+    
+    var result = new List<Token>();
+    add(token) => result.add(token);
+    
+    standaloneLineCheck() {
+     // Swallow leading whitespace 
+     // Note, the scanner will only ever create a single whitespace token. There
+     // is no need to handle multiple whitespace tokens.
+     if (isWhitespace(peek())
+         && isTag(peek(1))
+         && (isLineEnd(peek(2)) || peek(2) == null)) { // null == EOF
+       read();
+     } else if (isWhitespace(peek())
+         && isTag(peek(1))
+         && isWhitespace(peek(2))
+         && (isLineEnd(peek(3)) || peek(3) == null)) {
+       read();
+     }
+    
+     if ((isTag(peek()) && isLineEnd(peek(1)))
+         || (isTag(peek()) 
+             && isWhitespace(peek(1))
+             && (isLineEnd(peek(2)) || peek(2) == null))) {      
+    
+       // Add tag
+       add(read());
+    
+       // Swallow trailing whitespace.
+       if (isWhitespace(peek()))
+         read();
+    
+       // Swallow line end.
+       assert(isLineEnd(peek()));
+       read();
+    
+       standaloneLineCheck(); //FIXME don't use recursion.
+     }
+    }
+    
+    // Handle case where first line is a standalone tag.
+    standaloneLineCheck();
+    
+    var t;
+    while ((t = read()) != null) {
+     if (t.type == TokenType.lineEnd) {
+       // Convert line end to text token
+       add(new Token(TokenType.text, t.value, t.start, t.end));
+       standaloneLineCheck();
+     } else if (t.type == TokenType.whitespace) {
+       // Convert whitespace to text token
+       add(new Token(TokenType.text, t.value, t.start, t.end));
+     } else {
+       // Preserve token
+       add(t);
+     }
+    }
+    
+    return result;
+  }
+  
+  // Merging adjacent text nodes will improve the render speed, but slow down
+  // parsing. It will be beneficial where templates are parsed once and rendered
+  // a number of times.
+  List<Token> _mergeAdjacentText(List<Token> tokens) {
+    if (tokens.isEmpty) return <Token>[];
+    
+    var result = new List<Token>();
+    int i = 0;
+    while(i < tokens.length) {
+     var t = tokens[i];
+     
+     if (t.type != TokenType.text
+         || (i < tokens.length - 1 && tokens[i + 1].type != TokenType.text)) {
+       result.add(tokens[i]);
+       i++;
+     } else {
+       var buffer = new StringBuffer();
+       while(i < tokens.length && tokens[i].type == TokenType.text) {
+         buffer.write(tokens[i].value);
+         i++;
+       }
+       result.add(new Token(TokenType.text, buffer.toString(), t.start, t.end));
+     }
+    }
+    return result;
+  }
+
+}
+
diff --git a/lib/src/scanner.dart b/lib/src/scanner.dart
index 4120ce7..e370f45 100644
--- a/lib/src/scanner.dart
+++ b/lib/src/scanner.dart
@@ -7,6 +7,8 @@
 	   _lenient = lenient,

 	   _itr = source.runes.iterator {

 	  

+	  if (source == null) throw new ArgumentError.notNull('Template source');

+	  

 	  var delims = _parseDelimiterString(delimiters);

     _openDelimiter = delims[0];

     _openDelimiterInner = delims[1];

diff --git a/lib/src/scanner2.dart b/lib/src/scanner2.dart
index 37fe05f..2900c5b 100644
--- a/lib/src/scanner2.dart
+++ b/lib/src/scanner2.dart
@@ -1,4 +1,7 @@
-library scanner;
+library mustache.scanner;
+
+import 'token2.dart';
+import 'template_exception.dart';
 
 class Scanner {
   
@@ -68,7 +71,7 @@
           _peek() == _OPEN_MUSTACHE) {
         
         _read();
-        _push(TokenType.openTripleMustache, '{{{', start, _offset);
+        _push(TokenType.openDelimiter, '{{{', start, _offset);
         _scanTagContent();
         _scanCloseTripleMustache();
                    
@@ -132,6 +135,7 @@
     }
   }
   
+  // TODO rename this.
   _push(TokenType type, String value, int start, int end) =>
       _tokens.add(new Token(type, value, start, end));
   
@@ -257,7 +261,6 @@
 
   // Scan close triple mustache delimiter token.
   void _scanCloseTripleMustache() {
-    
     if (_peek() != _EOF) {
       int start = _offset;
       
@@ -265,9 +268,8 @@
       _expect(_CLOSE_MUSTACHE);      
       _expect(_CLOSE_MUSTACHE);
       
-      _push(TokenType.closeTripleMustache, '}}}', start, _offset);
-    }
-    
+      _push(TokenType.closeDelimiter, '}}}', start, _offset);
+    }    
   }  
 
   // Open delimiter characters and = have already been read.
@@ -388,168 +390,3 @@
 const int _MINUS = 45;
 
 
-
-
-class TokenType {
-  
-  const TokenType(this.name);
-  
-  final String name;
-  
-  String toString() => '(TokenType $name)';
-
-  static const TokenType text = const TokenType('text');
-  static const TokenType comment = const TokenType('comment');
-  static const TokenType openDelimiter = const TokenType('openDelimiter');
-  static const TokenType closeDelimiter = const TokenType('closeDelimiter');
-
-  // A sigil is the word commonly used to describe the special character at the
-  // start of mustache tag i.e. #, ^ or /.
-  static const TokenType sigil = const TokenType('sigil');
-  static const TokenType identifier = const TokenType('identifier');
-  static const TokenType dot = const TokenType('dot');
-  
-  static const TokenType changeDelimiter = const TokenType('changeDelimiter');
-  //TODO consider just using normal delimiter and checking the value to see if it is a triple
-  static const TokenType openTripleMustache = const TokenType('openTripleMustache');
-  static const TokenType closeTripleMustache = const TokenType('closeTripleMustache');
-  static const TokenType whitespace = const TokenType('whitespace');
-  static const TokenType lineEnd = const TokenType('lineEnd');
-
-}
-
-
-class Token {
-  
-  Token(this.type, this.value, this.start, this.end);
-  
-  final TokenType type;
-  final String value;
-  
-  final int start;
-  final int end;
-  
-  String toString() => "(Token ${type.name} \"$value\" $start $end)";
-  
-  // Only used for testing.
-  bool operator ==(o) => o is Token
-      && type == o.type
-      && value == o.value
-      && start == o.start
-      && end == o.end;
-  
-  // TODO hashcode. import quiver.
-}
-
-
-
-
-
-class TemplateException { //implements m.TemplateException {
-
-  TemplateException(this.message, this.templateName, this.source, this.offset);
-
-  final String message;
-  final String templateName;
-  final String source;
-  final int offset;
-  
-  bool _isUpdated = false;
-  int _line;
-  int _column;
-  String _context;
-  
-  int get line {
-    _update();
-    return _line;
-  }
-
-  int get column {
-    _update();
-    return _column;
-  }
-
-  String get context {
-    _update();
-    return _context;
-  }
-    
-  String toString() {
-    var list = [];
-    if (templateName != null) list.add(templateName);
-    if (line != null) list.add(line);
-    if (column != null) list.add(column);
-    var location = list.isEmpty ? '' : ' (${list.join(':')})';     
-    return '$message$location\n$context';
-  }
-
-  // This source code is a modified version of FormatException.toString().
-  void _update() {
-    if (_isUpdated) return;
-    _isUpdated = true;
-        
-    if (source == null
-        || offset == null
-        || (offset < 0 || offset > source.length))
-      return;
-    
-    // Find line and character column.
-    int lineNum = 1;
-    int lineStart = 0;
-    bool lastWasCR;
-    for (int i = 0; i < offset; i++) {
-      int char = source.codeUnitAt(i);
-      if (char == 0x0a) {
-        if (lineStart != i || !lastWasCR) {
-          lineNum++;
-        }
-        lineStart = i + 1;
-        lastWasCR = false;
-      } else if (char == 0x0d) {
-        lineNum++;
-        lineStart = i + 1;
-        lastWasCR = true;
-      }
-    }
-    
-    _line = lineNum;
-    _column = offset - lineStart + 1;
-
-    // Find context.
-    int lineEnd = source.length;
-    for (int i = offset; i < source.length; i++) {
-      int char = source.codeUnitAt(i);
-      if (char == 0x0a || char == 0x0d) {
-        lineEnd = i;
-        break;
-      }
-    }
-    int length = lineEnd - lineStart;
-    int start = lineStart;
-    int end = lineEnd;
-    String prefix = "";
-    String postfix = "";
-    if (length > 78) {
-      // Can't show entire line. Try to anchor at the nearest end, if
-      // one is within reach.
-      int index = offset - lineStart;
-      if (index < 75) {
-        end = start + 75;
-        postfix = "...";
-      } else if (end - offset < 75) {
-        start = end - 75;
-        prefix = "...";
-      } else {
-        // Neither end is near, just pick an area around the offset.
-        start = offset - 36;
-        end = offset + 36;
-        prefix = postfix = "...";
-      }
-    }
-    String slice = source.substring(start, end);
-    int markOffset = offset - start + prefix.length;
-    
-    _context = "$prefix$slice$postfix\n${" " * markOffset}^\n";
-  }
-
-}
\ No newline at end of file
diff --git a/lib/src/template.dart b/lib/src/template.dart
index db4cf3a..8ea5ed3 100644
--- a/lib/src/template.dart
+++ b/lib/src/template.dart
@@ -8,7 +8,7 @@
         String name,

         m.PartialResolver partialResolver})

        :  source = source,

-          _nodes = parse(source, lenient, name, '{{ }}'),

+          _nodes = parser.parse(source, lenient, name, '{{ }}'),

           _lenient = lenient,

           _htmlEscapeValues = htmlEscapeValues,

           _name = name,

diff --git a/lib/src/template_exception.dart b/lib/src/template_exception.dart
new file mode 100644
index 0000000..d4db9c0
--- /dev/null
+++ b/lib/src/template_exception.dart
@@ -0,0 +1,113 @@
+library mustache.template_exception;
+
+import 'package:mustache/mustache.dart' as m;
+
+class TemplateException implements m.TemplateException {
+
+  TemplateException(this.message, this.templateName, this.source, this.offset);
+
+  final String message;
+  final String templateName;
+  final String source;
+  final int offset;
+  
+  bool _isUpdated = false;
+  int _line;
+  int _column;
+  String _context;
+  
+  int get line {
+    _update();
+    return _line;
+  }
+
+  int get column {
+    _update();
+    return _column;
+  }
+
+  String get context {
+    _update();
+    return _context;
+  }
+    
+  String toString() {
+    var list = [];
+    if (templateName != null) list.add(templateName);
+    if (line != null) list.add(line);
+    if (column != null) list.add(column);
+    var location = list.isEmpty ? '' : ' (${list.join(':')})';     
+    return '$message$location\n$context';
+  }
+
+  // This source code is a modified version of FormatException.toString().
+  void _update() {
+    if (_isUpdated) return;
+    _isUpdated = true;
+        
+    if (source == null
+        || offset == null
+        || (offset < 0 || offset > source.length))
+      return;
+    
+    // Find line and character column.
+    int lineNum = 1;
+    int lineStart = 0;
+    bool lastWasCR;
+    for (int i = 0; i < offset; i++) {
+      int char = source.codeUnitAt(i);
+      if (char == 0x0a) {
+        if (lineStart != i || !lastWasCR) {
+          lineNum++;
+        }
+        lineStart = i + 1;
+        lastWasCR = false;
+      } else if (char == 0x0d) {
+        lineNum++;
+        lineStart = i + 1;
+        lastWasCR = true;
+      }
+    }
+    
+    _line = lineNum;
+    _column = offset - lineStart + 1;
+
+    // Find context.
+    int lineEnd = source.length;
+    for (int i = offset; i < source.length; i++) {
+      int char = source.codeUnitAt(i);
+      if (char == 0x0a || char == 0x0d) {
+        lineEnd = i;
+        break;
+      }
+    }
+    int length = lineEnd - lineStart;
+    int start = lineStart;
+    int end = lineEnd;
+    String prefix = "";
+    String postfix = "";
+    if (length > 78) {
+      // Can't show entire line. Try to anchor at the nearest end, if
+      // one is within reach.
+      int index = offset - lineStart;
+      if (index < 75) {
+        end = start + 75;
+        postfix = "...";
+      } else if (end - offset < 75) {
+        start = end - 75;
+        prefix = "...";
+      } else {
+        // Neither end is near, just pick an area around the offset.
+        start = offset - 36;
+        end = offset + 36;
+        prefix = postfix = "...";
+      }
+    }
+    String slice = source.substring(start, end);
+    int markOffset = offset - start + prefix.length;
+    
+    _context = "$prefix$slice$postfix\n${" " * markOffset}^\n";
+  }
+
+}
+
diff --git a/lib/src/token2.dart b/lib/src/token2.dart
new file mode 100644
index 0000000..682bc89
--- /dev/null
+++ b/lib/src/token2.dart
@@ -0,0 +1,49 @@
+library mustache.token;
+
+
+class TokenType {
+  
+  const TokenType(this.name);
+  
+  final String name;
+  
+  String toString() => '(TokenType $name)';
+
+  static const TokenType text = const TokenType('text');
+  static const TokenType openDelimiter = const TokenType('openDelimiter');
+  static const TokenType closeDelimiter = const TokenType('closeDelimiter');
+
+  // A sigil is the word commonly used to describe the special character at the
+  // start of mustache tag i.e. #, ^ or /.
+  static const TokenType sigil = const TokenType('sigil');
+  static const TokenType identifier = const TokenType('identifier');
+  static const TokenType dot = const TokenType('dot');
+  
+  static const TokenType changeDelimiter = const TokenType('changeDelimiter');
+  static const TokenType whitespace = const TokenType('whitespace');
+  static const TokenType lineEnd = const TokenType('lineEnd');
+
+}
+
+
+class Token {
+  
+  Token(this.type, this.value, this.start, this.end);
+  
+  final TokenType type;
+  final String value;
+  
+  final int start;
+  final int end;
+  
+  String toString() => "(Token ${type.name} \"$value\" $start $end)";
+  
+  // Only used for testing.
+  bool operator ==(o) => o is Token
+      && type == o.type
+      && value == o.value
+      && start == o.start
+      && end == o.end;
+  
+  // TODO hashcode. import quiver.
+}
diff --git a/test/parser_test.dart b/test/parser_test.dart
index 25f9aa7..59d6036 100644
--- a/test/parser_test.dart
+++ b/test/parser_test.dart
@@ -1,6 +1,9 @@
 import 'package:unittest/unittest.dart';
 
+import 'package:mustache/src/mustache_impl.dart' show TextNode, VariableNode, SectionNode;
+import 'package:mustache/src/parser.dart';
 import 'package:mustache/src/scanner2.dart';
+import 'package:mustache/src/token2.dart';
 
 main() {
   
@@ -83,28 +86,66 @@
      var tokens = scanner.scan();
      expect(tokens, orderedEquals([
        new Token(TokenType.text, 'abc', 0, 3),
-       new Token(TokenType.openTripleMustache, '{{{', 3, 6),
+       new Token(TokenType.openDelimiter, '{{{', 3, 6),
        new Token(TokenType.identifier, 'foo', 6, 9),
-       new Token(TokenType.closeTripleMustache, '}}}', 9, 12),
+       new Token(TokenType.closeDelimiter, '}}}', 9, 12),
        new Token(TokenType.text, 'def', 12, 15)
      ]));
    });
 
    
    test('scan triple mustache whitespace', () {
-     var source = 'abc{{{ foo }}}def';     
+     var source = 'abc{{{ foo }}}def';
      var scanner = new Scanner(source, 'foo', '{{ }}', lenient: false);
      var tokens = scanner.scan();
      expect(tokens, orderedEquals([
        new Token(TokenType.text, 'abc', 0, 3),
-       new Token(TokenType.openTripleMustache, '{{{', 3, 6),
+       new Token(TokenType.openDelimiter, '{{{', 3, 6),
        new Token(TokenType.whitespace, ' ', 6, 7),
        new Token(TokenType.identifier, 'foo', 7, 10),
        new Token(TokenType.whitespace, ' ', 10, 11),
-       new Token(TokenType.closeTripleMustache, '}}}', 11, 14),
+       new Token(TokenType.closeDelimiter, '}}}', 11, 14),
        new Token(TokenType.text, 'def', 14, 17)
      ]));
    });
+  });
+   
+  group('Parser', () {
+    
+   test('parse variable', () {
+     var source = 'abc{{foo}}def';
+     var parser = new Parser(source, 'foo', '{{ }}', lenient: false);
+     var nodes = parser.parse();
+     expect(nodes, orderedEquals([
+       new TextNode('abc', 0, 3),
+       new VariableNode('foo', 3, 10, escape: true),
+       new TextNode('def', 10, 13)
+     ]));
+   });
+
+   test('parse variable whitespace', () {
+     var source = 'abc{{ foo }}def';
+     var parser = new Parser(source, 'foo', '{{ }}', lenient: false);
+     var nodes = parser.parse();
+     expect(nodes, orderedEquals([
+       new TextNode('abc', 0, 3),
+       new VariableNode('foo', 3, 12, escape: true),
+       new TextNode('def', 12, 15)
+     ]));
+   });
+   
+   test('parse section', () {
+     var source = 'abc{{#foo}}def{{/foo}}ghi';
+     var parser = new Parser(source, 'foo', '{{ }}', lenient: false);
+     var nodes = parser.parse();
+     expect(nodes, orderedEquals([
+       new TextNode('abc', 0, 3),
+       new SectionNode('foo', 3, 11, '{{ }}'),
+       new TextNode('ghi', 22, 25)
+     ]));
+     expect(nodes[1].children, orderedEquals([new TextNode('def', 11, 14)]));
+   });
    
   });
+  
 }
\ No newline at end of file