Clean up some error messages
diff --git a/lib/src/char_reader.dart b/lib/src/char_reader.dart
index e474670..4b7dfd9 100644
--- a/lib/src/char_reader.dart
+++ b/lib/src/char_reader.dart
@@ -50,19 +50,15 @@
int peek() => _c;
- 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 _TemplateException(
- 'Unexpected end of input', null, null, 0);
+ String readWhile(bool test(int charCode), Function endOfFile) {
int start = _i;
while (peek() != _EOF && test(peek())) {
read();
}
+
+ if (peek() == _EOF && endOfFile != null) endOfFile();
int end = peek() == _EOF ? _source.length : _i;
return _source.substring(start, end);
diff --git a/lib/src/lambda_context.dart b/lib/src/lambda_context.dart
index 80c89df..37056a9 100644
--- a/lib/src/lambda_context.dart
+++ b/lib/src/lambda_context.dart
@@ -51,7 +51,8 @@
if (nodes.length == 1 && nodes.first.type == _TEXT)
return nodes.first.value;
- var source = _renderer._source.substring(_node.start, _node.end);
+ var source = _renderer._source.substring(
+ _node.contentStart, _node.contentEnd);
return source;
}
diff --git a/lib/src/node.dart b/lib/src/node.dart
index 7b3531c..44ab33e 100644
--- a/lib/src/node.dart
+++ b/lib/src/node.dart
@@ -4,18 +4,29 @@
_Node(this.type, this.value, this.start, this.end, {this.indent});
- _Node.fromToken(_Token token, {int start})
+ _Node.fromToken(_Token token)
: type = token.type,
value = token.value,
- start = start == null ? token.start : start,
+ start = token.start,
end = token.end,
indent = token.indent;
final int type;
final String value;
+
+ // 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
+ // section.
final int start;
- int end;
+ final int end;
+
+ int contentStart;
+ int contentEnd;
+
+ // Used to store the preceding whitespace before a partial tag, so that
+ // it's content can be correctly indented.
final String indent;
+
final List<_Node> children = new List<_Node>();
String toString() => '_Node: ${_tokenTypeString(type)}';
diff --git a/lib/src/parse.dart b/lib/src/parse.dart
index e523667..2fc0df5 100644
--- a/lib/src/parse.dart
+++ b/lib/src/parse.dart
@@ -45,7 +45,7 @@
checkTagChars(t);
// Store the start, end of the inner string content not
// including the tag.
- var child = new _Node.fromToken(t, start: t.end);
+ var child = new _Node.fromToken(t)..contentStart = t.end;
stack.last.children.add(child);
stack.add(child);
break;
@@ -59,7 +59,7 @@
templateName, source, t.start);
}
- stack.last.end = t.start;
+ stack.last.contentEnd = t.start;
stack.removeLast();
break;
@@ -91,7 +91,7 @@
// mediate list.
List<_Token> _removeStandaloneWhitespace(List<_Token> tokens) {
int i = 0;
- _Token read() { var ret = i < tokens.length ? tokens[i++] : null; /* print('Read: $ret'); */ return ret; }
+ _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
diff --git a/lib/src/renderer.dart b/lib/src/renderer.dart
index 198b15b..3a5744d 100644
--- a/lib/src/renderer.dart
+++ b/lib/src/renderer.dart
@@ -214,7 +214,7 @@
if (value == _noSuchProperty) {
if (!_lenient)
- throw _error('Value was missing, variable: ${node.value}', node);
+ throw _error('Value was missing for variable tag: ${node.value}.', node);
} else {
var valueString = (value == null) ? '' : value.toString();
var output = !escape || !_htmlEscapeValues
@@ -257,7 +257,7 @@
} else if (value == _noSuchProperty) {
if (!_lenient)
- throw _error('Value was missing, section: ${node.value}', node);
+ throw _error('Value was missing for section tag: ${node.value}.', node);
} else if (value is Function) {
var context = new _LambdaContext(node, this, isSection: true);
@@ -268,7 +268,7 @@
} else {
throw _error('Invalid value type for section, '
'section: ${node.value}, '
- 'type: ${value.runtimeType}', node);
+ 'type: ${value.runtimeType}.', node);
}
}
@@ -288,7 +288,7 @@
if (_lenient) {
_renderSectionWithValue(node, null);
} else {
- throw _error('Value was missing, inverse-section: ${node.value}', node);
+ throw _error('Value was missing for inverse section: ${node.value}.', node);
}
} else if (value is Function) {
@@ -299,7 +299,7 @@
throw _error(
'Invalid value type for inverse section, '
'section: ${node.value}, '
- 'type: ${value.runtimeType}, ', node);
+ 'type: ${value.runtimeType}.', node);
}
}
diff --git a/lib/src/scanner.dart b/lib/src/scanner.dart
index 3b5e513..31506b5 100644
--- a/lib/src/scanner.dart
+++ b/lib/src/scanner.dart
@@ -54,9 +54,6 @@
_templateName, _source, _r.offset);
}
}
-
- //FIXME unless in lenient mode only allow spaces.
- String _readTagWhitespace() => _r.readWhile(_isWhitespace);
bool _isWhitespace(int c)
=> const [_SPACE, _TAB , _NEWLINE, _RETURN].contains(c);
@@ -105,7 +102,7 @@
}
} else if (c == _SPACE || c == _TAB) {
- var value = _r.readWhile((c) => c == _SPACE || c == _TAB);
+ var value = _r.readWhile((c) => c == _SPACE || c == _TAB, null); //FIXME remove null.
_tokens.add(new _Token(_WHITESPACE, value, start, _r.offset));
//FIXME figure out why this is required
@@ -116,8 +113,8 @@
} else {
var value = _r.readWhile((c) => c != _openDelimiter
- && c != _EOF
- && c != _NEWLINE);
+ && c != _NEWLINE,
+ null); //FIXME remove null.
_tokens.add(new _Token(_TEXT, value, start, _r.offset));
}
}
@@ -176,24 +173,32 @@
_tokens.add(new _Token(_CHANGE_DELIMITER, value, start, _r.offset));
}
+ _errorEofInTag() => throw _error('Tag not closed before the end of the template.');
+
_scanTagWhitespace() {
const whitepsace = const [_SPACE, _NEWLINE, _RETURN, _TAB];
if (_lenient) {
- _r.readWhile(_isWhitespace);
+ _r.readWhile(_isWhitespace, _errorEofInTag);
} else {
- _r.readWhile((c) => c == _SPACE);
+ _r.readWhile((c) => c == _SPACE, _errorEofInTag);
if (_isWhitespace(_peek()))
throw _error('Tags may not contain newlines or tabs.');
}
}
String _scanTagIdentifier() {
+ //FIXME put this in a getter.
+ var delim = _closeDelimiterInner != null
+ ? _closeDelimiterInner
+ : _closeDelimiter;
if (_lenient) {
- return _closeDelimiterInner != null
- ? _r.readWhile((c) => c != _closeDelimiterInner) //FIXME reimplement readWhile to throw error on eof.
- : _r.readWhile((c) => c != _closeDelimiter);
+ return _r.readWhile((c) => c != delim, _errorEofInTag).trim();
} else {
- return _r.readWhile(_isAlphanum);
+ var id = _r.readWhile(_isAlphanum, _errorEofInTag);
+ _scanTagWhitespace();
+ if (_peek() != delim) throw _error('Unless in lenient mode tags may only '
+ 'contain the characters a-z, A-Z, minus, underscore and period.');
+ return id;
}
}
@@ -234,10 +239,8 @@
_scanTagWhitespace();
var identifier = _scanTagIdentifier();
-
- var value = identifier.trim();
- if (value.isEmpty) throw _error('Expected tag identifier.');
+ if (identifier.isEmpty) throw _error('Expected tag identifier.');
_scanTagWhitespace();
@@ -256,7 +259,7 @@
var type = sigils[sigil];
var indent = type == _PARTIAL ? _getPrecedingWhitespace() : '';
- _tokens.add(new _Token(type, value, start, _r.offset, indent: indent));
+ _tokens.add(new _Token(type, identifier, start, _r.offset, indent: indent));
}
// Capture whitespace preceding a partial tag so it can used for indentation
@@ -279,7 +282,7 @@
_scanTripleMustacheTag(int start) {
_expect(_OPEN_MUSTACHE);
- var value = _r.readWhile((c) => c != _CLOSE_MUSTACHE).trim();
+ var value = _r.readWhile((c) => c != _CLOSE_MUSTACHE, _errorEofInTag).trim();
//FIXME lenient/strict mode identifier parsing.
_expect(_CLOSE_MUSTACHE);
if (_closeDelimiterInner != null) _expect(_closeDelimiterInner);
@@ -289,8 +292,8 @@
_scanCommentTag(int start) {
var value = _closeDelimiterInner != null
- ? _r.readWhile((c) => c != _closeDelimiterInner).trim()
- : _r.readWhile((c) => c != _closeDelimiter).trim();
+ ? _r.readWhile((c) => c != _closeDelimiterInner, _errorEofInTag).trim()
+ : _r.readWhile((c) => c != _closeDelimiter, _errorEofInTag).trim();
if (_closeDelimiterInner != null) _expect(_closeDelimiterInner);
_expect(_closeDelimiter);
_tokens.add(new _Token(_COMMENT, value, start, _r.offset));
diff --git a/lib/src/template.dart b/lib/src/template.dart
index 921dc9d..6dbe3b1 100644
--- a/lib/src/template.dart
+++ b/lib/src/template.dart
@@ -71,7 +71,7 @@
if (templateName != null) list.add(templateName);
if (line != null) list.add(line);
if (column != null) list.add(column);
- var location = list.isEmpty ? '' : '(${list.join(':')})';
+ var location = list.isEmpty ? '' : ' (${list.join(':')})';
return '$message$location\n$context';
}
diff --git a/lib/src/token.dart b/lib/src/token.dart
index 7bff864..8e2e4dd 100644
--- a/lib/src/token.dart
+++ b/lib/src/token.dart
@@ -9,6 +9,9 @@
final int start;
final int end;
+
+ // Used to store the preceding whitespace before a partial tag, so that
+ // it's content can be correctly indented.
final String indent;
toString() => "${_tokenTypeString(type)}: "
diff --git a/test/mustache_test.dart b/test/mustache_test.dart
index 5395336..7ba3216 100644
--- a/test/mustache_test.dart
+++ b/test/mustache_test.dart
@@ -4,10 +4,10 @@
import 'package:mustache/mustache.dart';
const MISMATCHED_TAG = 'Mismatched tag';
-const UNEXPECTED_EOF = 'Unexpected end of input';
+const UNEXPECTED_EOF = 'Tag not closed';
const BAD_VALUE_SECTION = 'Invalid value type for section';
const BAD_VALUE_INV_SECTION = 'Invalid value type for inverse section';
-const BAD_TAG_NAME = 'Tag contained invalid characters in name';
+const BAD_TAG_NAME = 'Unless in lenient mode tags may only contain';
const VALUE_NULL = 'Value was null or missing';
Template parse(String source, {bool lenient: false})
@@ -115,7 +115,7 @@
"{{\t#foo }} {{ oi }} {{ /foo }}",
{'foo': [{'oi': 'OI!'}]},
' OI! ');
-
+
render(
"{{{ #foo }}} {{{ /foo }}}",
{'#foo': 1, '/foo': 2},
@@ -147,10 +147,11 @@
{'foo': true},
'true');
- render(
- "{{ > }}",
- {'>': 'oi'},
- '');
+//FIXME empty, or error in strict mode.
+// render(
+// "{{ > }}",
+// {'>': 'oi'},
+// '');
});
});
@@ -244,14 +245,14 @@
group('Invalid format', () {
test('Mismatched tag', () {
var source = '{{#section}}_{{var}}_{{/notsection}}';
- var ex = renderFail(source, {"section": {"var": "bob"}});
- expectFail(ex, 1, 25, 'Mismatched tag');
+ var ex = renderFail(source, {"section": {"var": "bob"}});
+ expectFail(ex, 1, 22, 'Mismatched tag');
});
test('Unexpected EOF', () {
var source = '{{#section}}_{{var}}_{{/section';
var ex = renderFail(source, {"section": {"var": "bob"}});
- expectFail(ex, 1, source.length + 2, UNEXPECTED_EOF);
+ expectFail(ex, 1, source.length, UNEXPECTED_EOF);
});
test('Bad tag name, open section', () {
@@ -351,7 +352,6 @@
lenient: false);
} catch (e) {
expect(e is TemplateException, isTrue);
- print(e);
threw = true;
}
expect(threw, isTrue);