Pass template name in error messages
diff --git a/lib/char_reader.dart b/lib/char_reader.dart
index ff3a3f1..1c95597 100644
--- a/lib/char_reader.dart
+++ b/lib/char_reader.dart
@@ -9,7 +9,7 @@
 
   _CharReader(String source)
       : _source = source,
-        _itr = source.runes.iterator {  //FIXME runes etc. Not sure if this is the right count.
+        _itr = source.runes.iterator {
         
     if (source == null)
       throw new ArgumentError('Source is null.');
@@ -50,8 +50,11 @@
   
   String readWhile(bool test(int charCode)) {
     
+    //FIXME provide template name. Or perhaps this is a programmer error
+    // and this shouldn't actually happen.
     if (peek() == _EOF)
-      throw new MustacheFormatException('Unexpected end of input.', line, column);
+      throw new MustacheFormatException(
+          'Unexpected end of input', null, line, column);
     
     int start = _i;
     
diff --git a/lib/mustache.dart b/lib/mustache.dart
index fff1d98..163f0bc 100644
--- a/lib/mustache.dart
+++ b/lib/mustache.dart
@@ -13,16 +13,18 @@
 /// with substituted values.
 /// Tag names may only contain characters a-z, A-Z, 0-9, underscore, and minus,
 /// unless lenient mode is specified.
-/// Throws [FormatException] if the syntax of the source is invalid.
+/// Throws [MustacheFormatException] if the syntax of the source is invalid.
 Template parse(String source,
-	             {bool lenient : false}) => _parse(source, lenient: lenient);
+ {bool lenient : false, String templateName}) => 
+    _parse(source, lenient: lenient, templateName: templateName);
+
 
 /// A Template can be rendered multiple times with different values.
 abstract class Template {
   
 	/// [values] can be a combination of Map, List, String. Any non-String object
 	/// will be converted using toString(). Null values will cause a 
-	/// FormatException, unless lenient module is enabled.
+	/// [MustacheFormatException], unless lenient module is enabled.
 	String renderString(values, {bool lenient : false, bool htmlEscapeValues : true});
 
 	/// [values] can be a combination of Map, List, String. Any non-String object
@@ -31,37 +33,54 @@
 	void render(values, StringSink sink, {bool lenient : false, bool htmlEscapeValues : true});
 }
 
-//TODO consider using FormatException, which prints nicer error messages.
-/// MustacheFormatException is used to obtain the line and column numbers
+
+/// [MustacheFormatException] is used to obtain the line and column numbers
 /// of the token which caused parse or render to fail.
 class MustacheFormatException implements Exception {
   
+  factory MustacheFormatException(
+      String message, String template, int line, int column) {
+    
+    var at = template == null
+        ? '$line:$column'
+        : '$template:$line:$column';
+    
+    return new MustacheFormatException._private(
+      '$message, at: $at.', template, line, column);
+  }  
+  
+  MustacheFormatException._private(
+      this.message, this.templateName, this.line, this.column);
+  
 	final String message;
 
+	final String templateName;
+	
 	/// The 1-based line number of the token where formatting error was found.
 	final int line;
 
 	/// The 1-based column number of the token where formatting error was found.
-	final int column;
-
-	MustacheFormatException(this.message, this.line, this.column);
+	final int column;	
 	
 	String toString() => message;
 	
 }
 
+
 //TODO does this require some sort of context to find partials nested in subdirs?
 typedef Template PartialResolver(String templateName);
 
+
 // Required for handing partials
 abstract class TemplateRenderer {
 
-  factory TemplateRenderer(PartialResolver partialResolver,
-      {bool lenient, bool htmlEscapeValues}) = _TemplateRenderer;
+  factory TemplateRenderer(
+      PartialResolver partialResolver,
+      {bool lenient,
+       bool htmlEscapeValues}) = _TemplateRenderer;
   
   String renderString(String templateName, values);
   
   void render(String templateName, values, StringSink sink);
 }
 
-
diff --git a/lib/scanner.dart b/lib/scanner.dart
index 6a591b9..d7f6ea2 100644
--- a/lib/scanner.dart
+++ b/lib/scanner.dart
@@ -141,8 +141,10 @@
 }

 

 class _Scanner {

-	_Scanner(String source) : _r = new _CharReader(source);

+	_Scanner(String source, [this._templateName])

+	 : _r = new _CharReader(source);

 

+	final String _templateName;

 	_CharReader _r;

 	List<_Token> _tokens = new List<_Token>();

 

@@ -166,13 +168,14 @@
 		int c = _read();

 

 		if (c == _EOF) {

-			throw new MustacheFormatException('Unexpected end of input.', _r.line, _r.column);

+			throw new MustacheFormatException('Unexpected end of input',

+			    _templateName, _r.line, _r.column);

 

 		} else if (c != expectedCharCode) {

 			throw new MustacheFormatException('Unexpected character, '

 				'expected: ${new String.fromCharCode(expectedCharCode)} ($expectedCharCode), '

-				'was: ${new String.fromCharCode(c)} ($c), '

-				'at: ${_r.line}:${_r.column}', _r.line, _r.column);

+				'was: ${new String.fromCharCode(c)} ($c)', 

+				_templateName, _r.line, _r.column);

 		}

 	}

 

@@ -254,7 +257,8 @@
 

 		switch(_peek()) {

 			case _EOF:

-				throw new MustacheFormatException('Unexpected end of input.', _r.line, _r.column);

+				throw new MustacheFormatException('Unexpected end of input',

+				    _templateName, _r.line, _r.column);

 

 			// Escaped text {{{ ... }}}

 			case _OPEN_MUSTACHE:				

diff --git a/lib/template.dart b/lib/template.dart
index 49b1c44..c41f857 100644
--- a/lib/template.dart
+++ b/lib/template.dart
@@ -5,34 +5,36 @@
 final RegExp _validTag = new RegExp(r'^[0-9a-zA-Z\_\-\.]+$');

 final RegExp _integerTag = new RegExp(r'^[0-9]+$');

 

-Template _parse(String source, {bool lenient : false, PartialResolver partialResolver}) {

+Template _parse(String source, 

+                {bool lenient : false,

+                 PartialResolver partialResolver,

+                 String templateName}) {

 	var tokens = _scan(source, lenient);

-	var ast = _parseTokens(tokens, lenient);

-	return new _Template(ast, lenient);

+	var ast = _parseTokens(tokens, lenient, templateName);

+	return new _Template(ast, lenient, templateName);

 }

 

-_Node _parseTokens(List<_Token> tokens, bool lenient) {

+_Node _parseTokens(List<_Token> tokens, bool lenient, String templateName) {

 	var stack = new List<_Node>()..add(new _Node(_OPEN_SECTION, 'root', 0, 0));

 	for (var t in tokens) {

 		if (const [_TEXT, _VARIABLE, _UNESC_VARIABLE, _PARTIAL].contains(t.type)) {

 			if (t.type == _VARIABLE || t.type == _UNESC_VARIABLE)

-			  _checkTagChars(t, lenient);

+			  _checkTagChars(t, lenient, templateName);

 			stack.last.children.add(new _Node.fromToken(t));

 

 		} else if (t.type == _OPEN_SECTION || t.type == _OPEN_INV_SECTION) {

-			_checkTagChars(t, lenient);

+			_checkTagChars(t, lenient, templateName);

 			var child = new _Node.fromToken(t);

 			stack.last.children.add(child);

 			stack.add(child);

 

 		} else if (t.type == _CLOSE_SECTION) {

-			_checkTagChars(t, lenient);

+			_checkTagChars(t, lenient, templateName);

 

 			if (stack.last.value != t.value) {

-				throw new MustacheFormatException('Mismatched tag, '

-					"expected: '${stack.last.value}', "

-					"was: '${t.value}', "

-					'at: ${t.line}:${t.column}.', t.line, t.column);

+				throw new MustacheFormatException(

+				  "Mismatched tag, expected: '${stack.last.value}', was: '${t.value}'",

+					templateName, t.line, t.column);

 			}

 

 			stack.removeLast();

@@ -41,6 +43,7 @@
 			// Do nothing

 

 		} else {

+		  //FIXME Use switch with enums so this becomes a compile time error.

 			throw new UnimplementedError();

 		}

 	}

@@ -48,20 +51,21 @@
 	return stack.last;

 }

 

-_checkTagChars(_Token t, bool lenient) {

+_checkTagChars(_Token t, bool lenient, String templateName) {

 		if (!lenient && !_validTag.hasMatch(t.value)) {

 			throw new MustacheFormatException(

-				'Tag contained invalid characters in name, '

-				'allowed: 0-9, a-z, A-Z, underscore, and minus, '

-				'at: ${t.line}:${t.column}.', t.line, t.column);

+			  'Tag contained invalid characters in name, '

+				'allowed: 0-9, a-z, A-Z, underscore, and minus',

+				templateName, t.line, t.column);

 		}

 }

 

 

 class _Template implements Template {

   

-  _Template(this._root, this._lenient);

+  _Template(this._root, this._lenient, this._name);

   

+  final String _name;

   final _Node _root;

   final bool _lenient;

   

@@ -80,7 +84,7 @@
                bool htmlEscapeValues : true,

                PartialResolver partialResolver}) {

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

-        lenient, htmlEscapeValues, partialResolver);

+        lenient, htmlEscapeValues, partialResolver, _name);

     renderer.render();

   }

 }

@@ -94,7 +98,8 @@
 	    this._stack,

 	    this._lenient,

 	    this._htmlEscapeValues,

-	    this._partialResolver);

+	    this._partialResolver,

+	    this._templateName);

 	

 	_Renderer.partial(_Renderer renderer, _Template partial)

       : this(partial._root,

@@ -103,7 +108,8 @@
           renderer._stack,

           renderer._lenient,

           renderer._htmlEscapeValues,

-          renderer._partialResolver);

+          renderer._partialResolver,

+          renderer._templateName);

 

 	final _Node _root;

   final StringSink _sink;

@@ -112,6 +118,7 @@
 	final bool _lenient;

 	final bool _htmlEscapeValues;

 	final PartialResolver _partialResolver;

+	final String _templateName;

 

 	void render() {

 		_root.children.forEach(_renderNode);

@@ -210,9 +217,8 @@
 		if (value == _NO_SUCH_PROPERTY) {

 			if (!_lenient)

 				throw new MustacheFormatException(

-					'Value was missing, '

-					'variable: ${node.value}, '

-					'at: ${node.line}:${node.column}.', node.line, node.column);

+				  'Value was missing, variable: ${node.value}',

+					_templateName, node.line, node.column);

 		} else {

 			var valueString = (value == null) ? '' : value.toString();

 			var output = !escape || !_htmlEscapeValues

@@ -243,15 +249,14 @@
 		} else if (value == _NO_SUCH_PROPERTY) {

 			if (!_lenient)

 				throw new MustacheFormatException(

-					'Value was missing, '

-					'section: ${node.value}, '

-					'at: ${node.line}:${node.column}.', node.line, node.column);

+				  'Value was missing, section: ${node.value}',

+					_templateName, node.line, node.column);

 		} else {

 			throw new MustacheFormatException(

-				'Invalid value type for section, '

+			  'Invalid value type for section, '

 				'section: ${node.value}, '

-				'type: ${value.runtimeType}, '

-				'at: ${node.line}:${node.column}.', node.line, node.column);

+        'type: ${value.runtimeType}',

+				_templateName, node.line, node.column);

 		}

 	}

 

@@ -268,16 +273,15 @@
 				_renderSectionWithValue(node, null);

 			} else {

 				throw new MustacheFormatException(

-					'Value was missing, '

-					'inverse-section: ${node.value}, '

-					'at: ${node.line}:${node.column}.', node.line, node.column);

+				    'Value was missing, inverse-section: ${node.value}',

+				    _templateName, node.line, node.column);

 			}

 		} else {

 			throw new MustacheFormatException(

-				'Invalid value type for inverse section, '

+			  'Invalid value type for inverse section, '

 				'section: ${node.value}, '

-				'type: ${value.runtimeType}, '

-				'at: ${node.line}:${node.column}.', node.line, node.column);

+				'type: ${value.runtimeType}, ',

+				_templateName, node.line, node.column);

 		}

 	}