Add more error messages, and change imports
diff --git a/lib/mustache.dart b/lib/mustache.dart
index 26d83b8..4a6ca6f 100644
--- a/lib/mustache.dart
+++ b/lib/mustache.dart
@@ -1,8 +1,8 @@
+/// [Mustache template documentation](http://mustache.github.com/mustache.5.html)
+
library mustache;
-import 'src/mustache_impl.dart' as impl;
-
-/// [Mustache template documentation](http://mustache.github.com/mustache.5.html)
+import 'src/template.dart' as t;
/// Use new Template(source) instead.
@deprecated
@@ -21,7 +21,7 @@
{bool lenient,
bool htmlEscapeValues,
String name,
- PartialResolver partialResolver}) = impl.Template.fromSource;
+ PartialResolver partialResolver}) = t.Template.fromSource;
String get name;
String get source;
diff --git a/lib/src/lambda_context.dart b/lib/src/lambda_context.dart
index 465dc65..b2e24e2 100644
--- a/lib/src/lambda_context.dart
+++ b/lib/src/lambda_context.dart
@@ -1,4 +1,11 @@
-part of mustache.impl;
+library mustache.lambda_context;
+
+import 'package:mustache/mustache.dart' as m;
+
+import 'node.dart';
+import 'parser.dart' as parser;
+import 'render_context.dart';
+import 'template_exception.dart';
/// Passed as an argument to a mustache lambda function.
class LambdaContext implements m.LambdaContext {
@@ -46,7 +53,7 @@
_checkClosed();
if (_node is! SectionNode) _error(
'LambdaContext.render() can only be called on section tags.');
- _renderSubtree(_context._sink, value);
+ _renderSubtree(_context.sink, value);
}
void write(Object object) {
@@ -83,7 +90,7 @@
delimiters = node.delimiters;
}
- var nodes = parse(source,
+ var nodes = parser.parse(source,
_context.lenient,
_context.templateName,
delimiters);
diff --git a/lib/src/mustache_impl.dart b/lib/src/mustache_impl.dart
deleted file mode 100644
index 0c11e5a..0000000
--- a/lib/src/mustache_impl.dart
+++ /dev/null
@@ -1,16 +0,0 @@
-library mustache.impl;
-
-@MirrorsUsed(metaTargets: const [m.mustache])
-import 'dart:mirrors';
-
-import 'package:mustache/mustache.dart' as m;
-
-import 'parser.dart' as parser;
-
-part 'lambda_context.dart';
-part 'node.dart';
-part 'parse.dart';
-part 'render_context.dart';
-part 'scanner.dart';
-part 'template.dart';
-part 'token.dart';
diff --git a/lib/src/node.dart b/lib/src/node.dart
index 670f453..d5653da 100644
--- a/lib/src/node.dart
+++ b/lib/src/node.dart
@@ -1,4 +1,8 @@
-part of mustache.impl;
+library mustache.node;
+
+import 'lambda_context.dart';
+import 'render_context.dart';
+import 'template.dart';
void renderWithContext(RenderContext ctx, List<Node> nodes) {
if (ctx.indent == null || ctx.indent == '') {
@@ -104,7 +108,7 @@
context.close();
}
- if (value == _noSuchProperty) {
+ if (value == noSuchProperty) {
if (!ctx.lenient)
throw ctx.error('Value was missing for variable tag: ${name}.', this);
} else {
@@ -201,7 +205,7 @@
} else if (value == false) {
// Do nothing.
- } else if (value == _noSuchProperty) {
+ } else if (value == noSuchProperty) {
if (!renderer.lenient)
throw renderer.error('Value was missing for section tag: ${name}.', this);
@@ -230,7 +234,7 @@
} else if (value == true || value is Map || value is Iterable) {
// Do nothing.
- } else if (value == _noSuchProperty) {
+ } else if (value == noSuchProperty) {
if (ctx.lenient) {
_renderWithValue(ctx, null);
} else {
@@ -284,7 +288,7 @@
: ctx.partialResolver(partialName);
if (template != null) {
var partialCtx = new RenderContext.partial(ctx, template, this.indent);
- renderWithContext(partialCtx, template._nodes);
+ renderWithContext(partialCtx, template.getNodes());
} else if (ctx.lenient) {
// do nothing
} else {
@@ -292,3 +296,11 @@
}
}
}
+
+const int _AMP = 38;
+const int _LT = 60;
+const int _GT = 62;
+const int _QUOTE = 34;
+const int _APOS = 39;
+const int _FORWARD_SLASH = 47;
+const int _NEWLINE = 10;
diff --git a/lib/src/parse.dart b/lib/src/parse.dart
deleted file mode 100644
index 5748a66..0000000
--- a/lib/src/parse.dart
+++ /dev/null
@@ -1,189 +0,0 @@
-part of mustache.impl;
-
-List<Node> parse(String source,
- bool lenient,
- String templateName,
- String delimiters) {
-
- if (source == null) throw new ArgumentError.notNull('Template source');
-
- var tokens =
- new Scanner(source, templateName, delimiters, lenient: lenient).scan();
-
- tokens = _removeStandaloneWhitespace(tokens);
- tokens = _mergeAdjacentText(tokens);
-
- var stack = new List<Node>()..add(new SectionNode('root', 0, 0, delimiters));
-
- var delim;
-
- for (var t in tokens) {
- switch (t.type) {
- case _TEXT:
- var n = new TextNode(t.value, t.start, t.end);
- stack.last.children.add(n);
- break;
-
- case _VARIABLE:
- case _UNESC_VARIABLE:
- var n = new VariableNode(
- t.value, t.start, t.end, escape: t.type != _UNESC_VARIABLE);
- stack.last.children.add(n);
- break;
-
- case _PARTIAL:
- var n = new PartialNode(t.value, t.start, t.end, t.indent);
- stack.last.children.add(n);
- break;
-
- case _OPEN_SECTION:
- case _OPEN_INV_SECTION:
- // Store the start, end of the inner string content not
- // including the tag.
- var child = new SectionNode(t.value, t.start, t.end, delim,
- inverse: t.type == _OPEN_INV_SECTION)
- ..contentStart = t.end;
- stack.last.children.add(child);
- stack.add(child);
- break;
-
- case _CLOSE_SECTION:
- if (stack.last.name != t.value) {
- throw new TemplateException(
- "Mismatched tag, expected: '${stack.last.name}', was: '${t.value}'",
- templateName, source, t.start);
- }
-
- stack.last.contentEnd = t.start;
-
- stack.removeLast();
- break;
-
- case _CHANGE_DELIMITER:
- delim = t.value;
- break;
-
- case _COMMENT:
- // Do nothing
- break;
-
- //FIXME change constants to enums, and then remove this default clause.
- default:
- throw new StateError('Unkown node type: $t');
- }
- }
-
- if (stack.length != 1) {
- throw new TemplateException(
- "Unclosed tag: '${stack.last.name}'.",
- templateName, source, stack.last.start);
- }
-
- return stack.last.children;
-}
-
-// 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 [_OPEN_SECTION, _OPEN_INV_SECTION, _CLOSE_SECTION, _COMMENT,
- _PARTIAL, _CHANGE_DELIMITER].contains(token.type);
-
- bool isWhitespace(token) => token != null && token.type == _WHITESPACE;
- bool isLineEnd(token) => token != null && token.type == _LINE_END;
-
- 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 == _LINE_END) {
- // Convert line end to text token
- add(new Token(_TEXT, t.value, t.start, t.end));
- standaloneLineCheck();
- } else if (t.type == _WHITESPACE) {
- // Convert whitespace to text token
- add(new Token(_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 != _TEXT
- || (i < tokens.length - 1 && tokens[i + 1].type != _TEXT)) {
- result.add(tokens[i]);
- i++;
- } else {
- var buffer = new StringBuffer();
- while(i < tokens.length && tokens[i].type == _TEXT) {
- buffer.write(tokens[i].value);
- i++;
- }
- result.add(new Token(_TEXT, buffer.toString(), t.start, t.end));
- }
- }
- return result;
-}
diff --git a/lib/src/parser.dart b/lib/src/parser.dart
index bdcb65c..c202eff 100644
--- a/lib/src/parser.dart
+++ b/lib/src/parser.dart
@@ -1,10 +1,9 @@
-library parser;
+library mustache.parser;
-//TODO just import nodes.
-import 'mustache_impl.dart' show Node, SectionNode, TextNode, PartialNode, VariableNode;
-import 'scanner2.dart';
+import 'node.dart';
+import 'scanner.dart';
import 'template_exception.dart';
-import 'token2.dart';
+import 'token.dart';
List<Node> parse(String source,
bool lenient,
@@ -20,8 +19,6 @@
final String name;
final int start;
final int end;
- //TODO parse the tag contents.
- //final List<List<String>> arguments;
}
class TagType {
@@ -49,7 +46,7 @@
TagType tagTypeFromString(String s) {
var type = _tagTypeMap[s];
- if (type == null) throw 'boom!'; //TODO unreachable.
+ if (type == null) throw new Exception('Unreachable code.');
return type;
}
@@ -60,7 +57,6 @@
// _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;
@@ -124,7 +120,11 @@
// {{/...}}
case TagType.closeSection:
- if (tag.name != _stack.last.name) throw 'boom!'; //TODO error message.
+ if (tag.name != _stack.last.name) {
+ throw new TemplateException("Mismatched tag, expected: "
+ "'${_stack.last.name}', was: '${tag.name}'",
+ _templateName, _source, tag.start);
+ }
var node = _stack.removeLast();
node.contentEnd = tag.start;
break;
@@ -141,11 +141,9 @@
case TagType.changeDelimiter:
// Ignore.
break;
-
- //TODO soon will be able to omit this as the analyzer will warn if a case
- // is missing for an enum.
+
default:
- throw 'boom!'; //TODO unreachable.
+ throw new Exception('Unreachable code.');
}
}
@@ -183,18 +181,18 @@
break;
case TokenType.lineEnd:
- //TODO the first line can be a standalone line too, and there is
- // no lineEnd. Perhaps _parseLine(firstLine: true)?
_parseLine();
break;
default:
- throw 'boom!'; //TODO error message.
+ throw new Exception('Unreachable code.');
}
}
- //TODO proper error message.
- assert(_stack.length == 1);
+ if (_stack.length != 1) {
+ throw new TemplateException("Unclosed tag: '${_stack.last.name}'.",
+ _templateName, _source, _stack.last.start);
+ }
return _stack.last.children;
}
@@ -319,51 +317,77 @@
node = null;
break;
- //TODO remove this default.
default:
- throw 'boom!'; //TODO error message. Unreachable.
+ throw new Exception('Unreachable code');
}
return node;
}
-
+
+ final RegExp _validIdentifier = new RegExp(r'^[0-9a-zA-Z\_\-\.]+$');
+
+ //TODO clean up EOF handling.
Tag _readTag() {
var open = _read();
-
+
+ checkEof() {
+ if (_peek() == null) {
+ throw _error(
+ 'Tag not closed before the end of the template.', open.start);
+ }
+ }
+
+ checkEof();
+
+ //TODO perhaps handle Eof in readif
//TODO _readIf()
if (_peek().type == TokenType.whitespace) _read();
+ checkEof();
+
// A sigil is the character which identifies which sort of tag it is,
// i.e. '#', '/', or '>'.
// Variable tags and triple mustache tags don't have a sigil.
TagType tagType = _peek().type == TokenType.sigil
? tagTypeFromString(_read().value)
: (open.value == '{{{' ? TagType.tripleMustache : TagType.variable);
-
- //TODO if tagType is comment, then ignore content. i.e. make sure parsing
- // doesn't crash.
+
+ checkEof();
if (_peek().type == TokenType.whitespace) _read();
+ checkEof();
+
// TODO split up names here instead of during render.
// Also check that they are valid token types.
- var name = _parseIdentifier();
-
- var close = _read();
-
- return new Tag(tagType, name, open.start, close.end);
- }
-
- //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();
+
+ checkEof();
- return name;
- }
+ // Check to see if tag name is valid.
+ if (tagType != TagType.comment) {
+ if (name == '') throw _error('Empty tag', open.start);
+
+ if (name.contains('\t') || name.contains('\n') || name.contains('\r')) {
+ throw _error('Tags may not contain newlines or tabs.', open.start);
+ }
+
+ if (!_lenient && !_validIdentifier.hasMatch(name)) {
+ throw _error('Unless in lenient mode, tags may only contain the '
+ 'characters a-z, A-Z, minus, underscore and period.', open.start);
+ }
+ }
+
+ var close = _read();
+
+ return new Tag(tagType, name, open.start, close.end);
+ }
+
+ TemplateException _error(String msg, int offset) =>
+ new TemplateException(msg, _templateName, _source, offset);
}
-
diff --git a/lib/src/render_context.dart b/lib/src/render_context.dart
index a3b6e78..8c7b3d9 100644
--- a/lib/src/render_context.dart
+++ b/lib/src/render_context.dart
@@ -1,13 +1,20 @@
-part of mustache.impl;
+library mustache.render_context;
+
+@MirrorsUsed(metaTargets: const [m.mustache])
+import 'dart:mirrors';
+import 'node.dart';
+import 'package:mustache/mustache.dart' as m;
+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();
+const Object noSuchProperty = const Object();
class RenderContext {
- RenderContext(this._sink,
+ RenderContext(this.sink,
List stack,
this.lenient,
this.htmlEscapeValues,
@@ -18,7 +25,7 @@
: _stack = new List.from(stack);
RenderContext.partial(RenderContext ctx, Template partial, String indent)
- : this(ctx._sink,
+ : this(ctx.sink,
ctx._stack,
ctx.lenient,
ctx.htmlEscapeValues,
@@ -52,7 +59,7 @@
ctx.indent + indent,
source);
- final StringSink _sink;
+ final StringSink sink;
final List _stack;
final bool lenient;
final bool htmlEscapeValues;
@@ -65,7 +72,7 @@
Object pop() => _stack.removeLast();
- write(Object output) => _sink.write(output.toString());
+ write(Object output) => sink.write(output.toString());
// Walks up the stack looking for the variable.
// Handles dotted names of the form "a.b.c".
@@ -74,16 +81,16 @@
return _stack.last;
}
var parts = name.split('.');
- var object = _noSuchProperty;
+ var object = noSuchProperty;
for (var o in _stack.reversed) {
object = _getNamedProperty(o, parts[0]);
- if (object != _noSuchProperty) {
+ if (object != noSuchProperty) {
break;
}
}
for (int i = 1; i < parts.length; i++) {
- if (object == null || object == _noSuchProperty) {
- return _noSuchProperty;
+ if (object == null || object == noSuchProperty) {
+ return noSuchProperty;
}
object = _getNamedProperty(object, parts[i]);
}
@@ -103,11 +110,11 @@
return object[int.parse(name)];
if (lenient && !_validTag.hasMatch(name))
- return _noSuchProperty;
+ return noSuchProperty;
var instance = reflect(object);
var field = instance.type.instanceMembers[new Symbol(name)];
- if (field == null) return _noSuchProperty;
+ if (field == null) return noSuchProperty;
var invocation = null;
if ((field is VariableMirror) || ((field is MethodMirror) && (field.isGetter))) {
@@ -116,7 +123,7 @@
invocation = instance.invoke(field.simpleName, []);
}
if (invocation == null) {
- return _noSuchProperty;
+ return noSuchProperty;
}
return invocation.reflectee;
}
diff --git a/lib/src/scanner.dart b/lib/src/scanner.dart
index e370f45..8c0b248 100644
--- a/lib/src/scanner.dart
+++ b/lib/src/scanner.dart
@@ -1,388 +1,395 @@
-part of mustache.impl;
-
-class Scanner {
-
- Scanner(String source, this._templateName, String delimiters, {bool lenient: true})
- : _source = source,
- _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];
- _closeDelimiterInner = delims[2];
- _closeDelimiter = delims[3];
-
- if (source == '') {
- _c = _EOF;
- } else {
- _itr.moveNext();
- _c = _itr.current;
- }
- }
-
- final String _templateName;
- final String _source;
- final bool _lenient;
-
- final Iterator<int> _itr;
- int _offset = 0;
- int _c = 0;
-
- final List<Token> _tokens = new List<Token>();
-
- // These can be changed by the change delimiter tag.
- int _openDelimiter;
- int _openDelimiterInner;
- int _closeDelimiterInner;
- int _closeDelimiter;
-
- List<Token> scan() {
- while(true) {
- int c = _peek();
- if (c == _EOF) break;
- else if (c == _openDelimiter) _scanMustacheTag();
- else _scanText();
- }
- return _tokens;
- }
-
- int _peek() => _c;
-
- int _read() {
- var c = _c;
- if (_itr.moveNext()) {
- _offset++;
- _c = _itr.current;
- } else {
- _c = _EOF;
- }
- return c;
- }
-
- String _readWhile(bool test(int charCode), [Function endOfFile]) {
-
- int start = _offset;
- while (_peek() != _EOF && test(_peek())) {
- _read();
- }
-
- if (_peek() == _EOF && endOfFile != null) endOfFile();
-
- int end = _peek() == _EOF ? _source.length : _offset;
- return _source.substring(start, end);
- }
-
- _expect(int expectedCharCode) {
- int c = _read();
-
- if (c == _EOF) {
- throw new TemplateException('Unexpected end of input',
- _templateName, _source, _offset);
-
- } else if (c != expectedCharCode) {
- throw new TemplateException('Unexpected character, '
- 'expected: ${new String.fromCharCode(expectedCharCode)} ($expectedCharCode), '
- 'was: ${new String.fromCharCode(c)} ($c)',
- _templateName, _source, _offset);
- }
- }
-
- bool _isWhitespace(int c)
- => const [_SPACE, _TAB , _NEWLINE, _RETURN].contains(c);
-
- // A sigil is the word commonly used to describe the special character at the
- // start of mustache tag i.e. #, ^ or /.
- bool _isSigil(int c)
- => const [_HASH, _CARET, _FORWARD_SLASH, _GT, _AMP, _EXCLAIM, _EQUAL]
- .contains(c);
-
- bool _isAlphanum(int c)
- => (c >= _a && c <= _z)
- || (c >= _A && c <= _Z)
- || (c >= _0 && c <= _9)
- || c == _MINUS
- || c == _UNDERSCORE
- || c == _PERIOD;
-
- _scanText() {
-
- while(true) {
- int c = _peek();
- int start = _offset;
-
- if (c == _EOF) {
- return;
-
- } else if (c == _openDelimiter) {
- return;
-
- // Newlines and whitespace have separate tokens so the standalone lines
- // logic can be implemented.
- } else if (c == _NEWLINE) {
- _read();
- var value = new String.fromCharCode(c);
- _tokens.add(new Token(_LINE_END, value, start, _offset));
-
- } else if (c == _RETURN) {
- _read();
- if (_peek() == _NEWLINE) {
- _read();
- _tokens.add(new Token(_LINE_END, '\r\n', start, _offset));
- } else {
- _tokens.add(new Token(_TEXT, '\r', start, _offset));
- }
-
- } else if (c == _SPACE || c == _TAB) {
- var value = _readWhile((c) => c == _SPACE || c == _TAB);
- _tokens.add(new Token(_WHITESPACE, value, start, _offset));
-
- } else {
- var value = _readWhile((c) => c != _openDelimiter && c != _NEWLINE);
- _tokens.add(new Token(_TEXT, value, start, _offset));
- }
- }
- }
-
- void _scanMustacheTag() {
- int start = _offset;
- int sigil = 0;
-
- _expect(_openDelimiter);
-
- // If just a single delimeter character then this is a text token.
- if (_openDelimiterInner != null && _peek() != _openDelimiterInner) {
- var value = new String.fromCharCode(_openDelimiter);
- _tokens.add(new Token(_TEXT, value, start, _offset));
- return;
- }
-
- if (_openDelimiterInner != null) _expect(_openDelimiterInner);
-
- if (_peek() == _OPEN_MUSTACHE) {
- _scanTripleMustacheTag(start);
- return;
- }
-
- _scanTagWhitespace();
-
- if (_isSigil(_peek())) sigil = _read();
-
- if (sigil == _EQUAL) {
- _scanChangeDelimiterTag(start);
- return;
- } else if (sigil == _EXCLAIM) {
- _scanCommentTag(start);
- return;
- }
-
- _scanTagWhitespace();
-
- var identifier = _scanTagIdentifier();
-
- if (identifier.isEmpty) throw _error('Expected tag identifier.');
-
- _scanTagWhitespace();
-
- if (_closeDelimiterInner != null) _expect(_closeDelimiterInner);
- _expect(_closeDelimiter);
-
- const sigils = const <int, int> {
- 0: _VARIABLE,
- _HASH: _OPEN_SECTION,
- _FORWARD_SLASH: _CLOSE_SECTION,
- _CARET: _OPEN_INV_SECTION,
- _GT: _PARTIAL,
- _AMP: _UNESC_VARIABLE
- };
-
- var type = sigils[sigil];
- var indent = type == _PARTIAL ? _getPrecedingWhitespace() : '';
-
- _tokens.add(new Token(type, identifier, start, _offset, indent: indent));
- }
-
- _errorEofInTag() => throw _error('Tag not closed before the end of the template.');
-
- _scanTagWhitespace() {
- if (_lenient) {
- _readWhile(_isWhitespace, _errorEofInTag);
- } else {
- _readWhile((c) => c == _SPACE, _errorEofInTag);
- if (_isWhitespace(_peek()))
- throw _error('Tags may not contain newlines or tabs.');
- }
- }
-
- String _scanTagIdentifier({bool tripleMo: false}) {
- var delim = _closeDelimiterInner != null
- ? _closeDelimiterInner
- : _closeDelimiter;
- if (_lenient) {
- return _readWhile(
- (c) => c != delim
- || tripleMo && c != _CLOSE_MUSTACHE,
- _errorEofInTag).trim();
- } else {
- var id = _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;
- }
- }
-
- // Capture whitespace preceding a partial tag so it can used for indentation
- // during rendering.
- String _getPrecedingWhitespace() {
- var indent = '';
- if (_tokens.isNotEmpty) {
- if (_tokens.length == 1 && _tokens.last.type == _WHITESPACE) {
- indent = _tokens.last.value;
-
- } else if (_tokens.length > 1) {
- if (_tokens.last.type == _WHITESPACE
- && _tokens[_tokens.length - 2].type == _NEWLINE) {
- indent = _tokens.last.value;
- }
- }
- }
- return indent;
- }
-
- void _scanTripleMustacheTag(int start) {
- _expect(_OPEN_MUSTACHE);
- var value = _scanTagIdentifier();
- _expect(_CLOSE_MUSTACHE);
- if (_closeDelimiterInner != null) _expect(_closeDelimiterInner);
- _expect(_closeDelimiter);
- _tokens.add(new Token(_UNESC_VARIABLE, value, start, _offset));
- }
-
- void _scanCommentTag(int start) {
- var value = _closeDelimiterInner != null
- ? _readWhile((c) => c != _closeDelimiterInner, _errorEofInTag).trim()
- : _readWhile((c) => c != _closeDelimiter, _errorEofInTag).trim();
- if (_closeDelimiterInner != null) _expect(_closeDelimiterInner);
- _expect(_closeDelimiter);
- _tokens.add(new Token(_COMMENT, value, start, _offset));
- }
-
- //TODO consider changing the parsing here to use a regexp. It will probably
- // be simpler to read.
- void _scanChangeDelimiterTag(int start) {
- // Open delimiter characters and = have already been read.
-
- var delimiterInner = _closeDelimiterInner;
- var delimiter = _closeDelimiter;
-
- _scanTagWhitespace();
-
- int c;
- c = _read();
-
- if (c == _EQUAL) throw _error('Incorrect change delimiter tag.');
- _openDelimiter = c;
-
- c = _read();
- if (_isWhitespace(c)) {
- _openDelimiterInner = null;
- } else {
- _openDelimiterInner = c;
- }
-
- _scanTagWhitespace();
-
- c = _read();
-
- if (_isWhitespace(c) || c == _EQUAL)
- throw _error('Incorrect change delimiter tag.');
-
- if (_isWhitespace(_peek()) || _peek() == _EQUAL) {
- _closeDelimiterInner = null;
- _closeDelimiter = c;
- } else {
- _closeDelimiterInner = c;
- _closeDelimiter = _read();
- }
-
- _scanTagWhitespace();
- _expect(_EQUAL);
- _scanTagWhitespace();
-
- _expect(delimiterInner);
- _expect(delimiter);
-
- var value = _delimiterString(
- _openDelimiter,
- _openDelimiterInner,
- _closeDelimiterInner,
- _closeDelimiter);
-
- _tokens.add(new Token(_CHANGE_DELIMITER, value, start, _offset));
- }
-
- m.TemplateException _error(String message) {
- return new TemplateException(message, _templateName, _source, _offset);
- }
-
-}
-
-_delimiterString(int open, int openInner, int closeInner, int close) {
- var buffer = new StringBuffer();
- buffer.writeCharCode(open);
- if (openInner != null) buffer.writeCharCode(openInner);
- buffer.write(' ');
- if (closeInner != null) buffer.writeCharCode(closeInner);
- buffer.writeCharCode(close);
- return buffer.toString();
-}
-
-List<int> _parseDelimiterString(String s) {
- if (s == null) return [_OPEN_MUSTACHE, _OPEN_MUSTACHE,
- _CLOSE_MUSTACHE, _CLOSE_MUSTACHE];
- if (s.length == 3) {
- return [s.codeUnits[0], null, null, s.codeUnits[2]];
-
- } else if (s.length == 5) {
- return [s.codeUnits[0],
- s.codeUnits[1],
- s.codeUnits[3],
- s.codeUnits[4]];
- } else {
- throw new TemplateException(
- 'Invalid delimiter string $s', null, null, null);
- }
-}
-
-const int _EOF = -1;
-const int _TAB = 9;
-const int _NEWLINE = 10;
-const int _RETURN = 13;
-const int _SPACE = 32;
-const int _EXCLAIM = 33;
-const int _QUOTE = 34;
-const int _APOS = 39;
-const int _HASH = 35;
-const int _AMP = 38;
-const int _PERIOD = 46;
-const int _FORWARD_SLASH = 47;
-const int _LT = 60;
-const int _EQUAL = 61;
-const int _GT = 62;
-const int _CARET = 94;
-
-const int _OPEN_MUSTACHE = 123;
-const int _CLOSE_MUSTACHE = 125;
-
-const int _A = 65;
-const int _Z = 90;
-const int _a = 97;
-const int _z = 122;
-const int _0 = 48;
-const int _9 = 57;
-
-const int _UNDERSCORE = 95;
-const int _MINUS = 45;
+library mustache.scanner;
+
+import 'token.dart';
+import 'template_exception.dart';
+
+class Scanner {
+
+ Scanner(String source, this._templateName, String delimiters,
+ {bool lenient: true})
+ : _source = source,
+ _lenient = lenient,
+ _itr = source.runes.iterator {
+
+ var delims = _parseDelimiterString(delimiters);
+ _openDelimiter = delims[0];
+ _openDelimiterInner = delims[1];
+ _closeDelimiterInner = delims[2];
+ _closeDelimiter = delims[3];
+
+ if (source == '') {
+ _c = _EOF;
+ } else {
+ _itr.moveNext();
+ _c = _itr.current;
+ }
+ }
+
+ final String _templateName;
+ final String _source;
+ final bool _lenient;
+
+ final Iterator<int> _itr;
+ int _offset = 0;
+ int _c = 0;
+
+ final List<Token> _tokens = new List<Token>();
+
+ // These can be changed by the change delimiter tag.
+ int _openDelimiter;
+ int _openDelimiterInner;
+ int _closeDelimiterInner;
+ int _closeDelimiter;
+
+ List<Token> scan() {
+
+ for (int c = _peek(); c != _EOF; c = _peek()) {
+
+ // Scan text tokens.
+ if (c != _openDelimiter) {
+ _scanText();
+ continue;
+ }
+
+ int start = _offset;
+
+ // Read first open delimiter character.
+ _read();
+
+ // If only a single delimiter character then create a text token.
+ if (_openDelimiterInner != null && _peek() != _openDelimiterInner) {
+ var value = new String.fromCharCode(_openDelimiter);
+ _append(TokenType.text, value, start, _offset);
+ continue;
+ }
+
+ if (_openDelimiterInner != null) _expect(_openDelimiterInner);
+
+ // Handle triple mustache.
+ if (_openDelimiterInner == _OPEN_MUSTACHE &&
+ _openDelimiter == _OPEN_MUSTACHE &&
+ _peek() == _OPEN_MUSTACHE) {
+
+ _read();
+ _append(TokenType.openDelimiter, '{{{', start, _offset);
+ _scanTagContent();
+ _scanCloseTripleMustache();
+
+ } else {
+
+ // Check to see if this is a change delimiter tag. {{= | | =}}
+ // Need to skip whitespace and check for "=".
+ int wsStart = _offset;
+ var ws = _readWhile(_isWhitespace);
+
+ if (_peek() == _EQUAL) {
+ _parseChangeDelimiterTag(start);
+
+ } else {
+ // Scan standard mustache tag.
+ var value = new String.fromCharCodes(_openDelimiterInner == null
+ ? [_openDelimiter]
+ : [_openDelimiter, _openDelimiterInner]);
+
+ _append(TokenType.openDelimiter, value, start, wsStart);
+
+ if (ws != '') _append(TokenType.whitespace, ws, wsStart, _offset);
+
+ _scanTagContent();
+ _scanCloseDelimiter();
+ }
+ }
+ }
+ return _tokens;
+ }
+
+ int _peek() => _c;
+
+ int _read() {
+ int c = _c;
+ _offset++;
+ _c = _itr.moveNext() ? _itr.current : _EOF;
+ return c;
+ }
+
+ String _readWhile(bool test(int charCode)) {
+ int start = _offset;
+ while (_peek() != _EOF && test(_peek())) {
+ _read();
+ }
+ int end = _peek() == _EOF ? _source.length : _offset;
+ return _source.substring(start, end);
+ }
+
+ //TODO clean up error handling.
+ _expect(int expectedCharCode) {
+ int c = _read();
+
+ if (c == _EOF) {
+ throw new TemplateException('Unexpected end of input',
+ _templateName, _source, _offset);
+
+ } else if (c != expectedCharCode) {
+ throw new TemplateException('Unexpected character, '
+ 'expected: ${new String.fromCharCode(expectedCharCode)}, '
+ 'was: ${new String.fromCharCode(c)}', _templateName, _source, _offset);
+ }
+ }
+
+ _append(TokenType type, String value, int start, int end) =>
+ _tokens.add(new Token(type, value, start, end));
+
+ bool _isWhitespace(int c)
+ => const [_SPACE, _TAB , _NEWLINE, _RETURN].contains(c);
+
+ // Scan text. This adds text tokens, line end tokens, and whitespace
+ // tokens for whitespace at the begining of a line. This is because the
+ // mustache spec requires special handing of whitespace.
+ void _scanText() {
+ int start = 0;
+ TokenType token;
+ String value;
+
+ for (int c = _peek(); c != _EOF && c != _openDelimiter; c = _peek()) {
+ start = _offset;
+
+ switch (c) {
+ case _SPACE:
+ case _TAB:
+ value = _readWhile((c) => c == _SPACE || c == _TAB);
+ token = TokenType.whitespace;
+ break;
+
+ case _NEWLINE:
+ _read();
+ token = TokenType.lineEnd;
+ value = '\n';
+ break;
+
+ case _RETURN:
+ _read();
+ if (_peek() == _NEWLINE) {
+ _read();
+ token = TokenType.lineEnd;
+ value = '\r\n';
+ } else {
+ token = TokenType.text;
+ value = '\r';
+ }
+ break;
+
+ default:
+ value = _readWhile((c) => c != _openDelimiter && c != _NEWLINE);
+ token = TokenType.text;
+ }
+
+ _append(token, value, start, _offset);
+ }
+ }
+
+ // Scan contents of a tag and the end delimiter token.
+ void _scanTagContent() {
+
+ int start;
+ TokenType token;
+ String value;
+ List<Token> result = <Token>[];
+
+ bool isCloseDelimiter(int c) =>
+ (_closeDelimiterInner == null && c == _closeDelimiter)
+ || (_closeDelimiterInner != null && c == _closeDelimiterInner);
+
+ for (int c = _peek(); c != _EOF && !isCloseDelimiter(c); c = _peek()) {
+
+ start = _offset;
+
+ switch (c) {
+ case _HASH:
+ case _CARET:
+ case _FORWARD_SLASH:
+ case _GT:
+ case _AMP:
+ case _EXCLAIM:
+ _read();
+ token = TokenType.sigil;
+ value = new String.fromCharCode(c);
+ break;
+
+ case _SPACE:
+ case _TAB:
+ case _NEWLINE:
+ case _RETURN:
+ token = TokenType.whitespace;
+ value = _readWhile(_isWhitespace);
+ break;
+
+ case _PERIOD:
+ _read();
+ token = TokenType.dot;
+ value = '.';
+ break;
+
+ default:
+ // Indentifier can be any other character in lenient mode.
+ token = TokenType.identifier;
+ value = _readWhile((c) => !(const [ _HASH, _CARET, _FORWARD_SLASH,
+ _GT, _AMP, _EXCLAIM, _EQUAL, _SPACE, _TAB, _NEWLINE, _RETURN,
+ _PERIOD].contains(c)) &&
+ c != _closeDelimiterInner &&
+ c != _closeDelimiter);
+ }
+ _append(token, value, start, _offset);
+ }
+ }
+
+ // Scan close delimiter token.
+ void _scanCloseDelimiter() {
+
+ if (_peek() != _EOF) {
+ int start = _offset;
+
+ if (_closeDelimiterInner != null) _expect(_closeDelimiterInner);
+ _expect(_closeDelimiter);
+
+ String value = new String.fromCharCodes(_closeDelimiterInner == null
+ ? [_closeDelimiter]
+ : [_closeDelimiterInner, _closeDelimiter]);
+
+ _append(TokenType.closeDelimiter, value, start, _offset);
+ }
+ }
+
+ // Scan close triple mustache delimiter token.
+ void _scanCloseTripleMustache() {
+ if (_peek() != _EOF) {
+ int start = _offset;
+
+ _expect(_CLOSE_MUSTACHE);
+ _expect(_CLOSE_MUSTACHE);
+ _expect(_CLOSE_MUSTACHE);
+
+ _append(TokenType.closeDelimiter, '}}}', start, _offset);
+ }
+ }
+
+ //TODO EOF handling
+ // Open delimiter characters have already been read.
+ void _parseChangeDelimiterTag(int start) {
+
+ _expect(_EQUAL);
+
+ var delimiterInner = _closeDelimiterInner;
+ var delimiter = _closeDelimiter;
+
+ _readWhile(_isWhitespace);
+
+ int c;
+ c = _read();
+
+ if (c == _EQUAL) throw _error('Incorrect change delimiter tag.');
+ _openDelimiter = c;
+
+ c = _read();
+ if (_isWhitespace(c)) {
+ _openDelimiterInner = null;
+ } else {
+ _openDelimiterInner = c;
+ }
+
+ _readWhile(_isWhitespace);
+
+ c = _read();
+
+ if (_isWhitespace(c) || c == _EQUAL)
+ throw _error('Incorrect change delimiter tag.');
+
+ if (_isWhitespace(_peek()) || _peek() == _EQUAL) {
+ _closeDelimiterInner = null;
+ _closeDelimiter = c;
+ } else {
+ _closeDelimiterInner = c;
+ _closeDelimiter = _read();
+ }
+
+ _readWhile(_isWhitespace);
+
+ _expect(_EQUAL);
+
+ _readWhile(_isWhitespace);
+
+ if (delimiterInner != null) _expect(delimiterInner);
+ _expect(delimiter);
+
+ var value = _delimiterString(
+ _openDelimiter,
+ _openDelimiterInner,
+ _closeDelimiterInner,
+ _closeDelimiter);
+
+ _append(TokenType.changeDelimiter, value, start, _offset);
+ }
+
+ TemplateException _error(String message) {
+ return new TemplateException(message, _templateName, _source, _offset);
+ }
+
+}
+
+_delimiterString(int open, int openInner, int closeInner, int close) {
+ var buffer = new StringBuffer();
+ buffer.writeCharCode(open);
+ if (openInner != null) buffer.writeCharCode(openInner);
+ buffer.write(' ');
+ if (closeInner != null) buffer.writeCharCode(closeInner);
+ buffer.writeCharCode(close);
+ return buffer.toString();
+}
+
+List<int> _parseDelimiterString(String s) {
+ if (s == null) return [_OPEN_MUSTACHE, _OPEN_MUSTACHE,
+ _CLOSE_MUSTACHE, _CLOSE_MUSTACHE];
+ if (s.length == 3) {
+ return [s.codeUnits[0], null, null, s.codeUnits[2]];
+
+ } else if (s.length == 5) {
+ return [s.codeUnits[0],
+ s.codeUnits[1],
+ s.codeUnits[3],
+ s.codeUnits[4]];
+ } else {
+ throw new TemplateException(
+ 'Invalid delimiter string $s', null, null, null);
+ }
+}
+
+const int _EOF = -1;
+const int _TAB = 9;
+const int _NEWLINE = 10;
+const int _RETURN = 13;
+const int _SPACE = 32;
+const int _EXCLAIM = 33;
+const int _QUOTE = 34;
+const int _APOS = 39;
+const int _HASH = 35;
+const int _AMP = 38;
+const int _PERIOD = 46;
+const int _FORWARD_SLASH = 47;
+const int _LT = 60;
+const int _EQUAL = 61;
+const int _GT = 62;
+const int _CARET = 94;
+
+const int _OPEN_MUSTACHE = 123;
+const int _CLOSE_MUSTACHE = 125;
+
+const int _A = 65;
+const int _Z = 90;
+const int _a = 97;
+const int _z = 122;
+const int _0 = 48;
+const int _9 = 57;
+
+const int _UNDERSCORE = 95;
+const int _MINUS = 45;
+
+
diff --git a/lib/src/scanner2.dart b/lib/src/scanner2.dart
deleted file mode 100644
index d5af146..0000000
--- a/lib/src/scanner2.dart
+++ /dev/null
@@ -1,394 +0,0 @@
-library mustache.scanner;
-
-import 'token2.dart';
-import 'template_exception.dart';
-
-class Scanner {
-
- Scanner(String source, this._templateName, String delimiters,
- {bool lenient: true})
- : _source = source,
- _lenient = lenient,
- _itr = source.runes.iterator {
-
- var delims = _parseDelimiterString(delimiters);
- _openDelimiter = delims[0];
- _openDelimiterInner = delims[1];
- _closeDelimiterInner = delims[2];
- _closeDelimiter = delims[3];
-
- if (source == '') {
- _c = _EOF;
- } else {
- _itr.moveNext();
- _c = _itr.current;
- }
- }
-
- final String _templateName;
- final String _source;
- final bool _lenient;
-
- final Iterator<int> _itr;
- int _offset = 0;
- int _c = 0;
-
- final List<Token> _tokens = new List<Token>();
-
- // These can be changed by the change delimiter tag.
- int _openDelimiter;
- int _openDelimiterInner;
- int _closeDelimiterInner;
- int _closeDelimiter;
-
- List<Token> scan() {
-
- for (int c = _peek(); c != _EOF; c = _peek()) {
-
- // Scan text tokens.
- if (c != _openDelimiter) {
- _scanText();
- continue;
- }
-
- int start = _offset;
-
- // Read first open delimiter character.
- _read();
-
- // If only a single delimiter character then create a text token.
- if (_openDelimiterInner != null && _peek() != _openDelimiterInner) {
- var value = new String.fromCharCode(_openDelimiter);
- _push(TokenType.text, value, start, _offset);
- continue;
- }
-
- if (_openDelimiterInner != null) _expect(_openDelimiterInner);
-
- // Handle triple mustache.
- if (_openDelimiterInner == _OPEN_MUSTACHE &&
- _openDelimiter == _OPEN_MUSTACHE &&
- _peek() == _OPEN_MUSTACHE) {
-
- _read();
- _push(TokenType.openDelimiter, '{{{', start, _offset);
- _scanTagContent();
- _scanCloseTripleMustache();
-
- } else {
-
- // Check to see if this is a change delimiter tag. {{= | | =}}
- // Need to skip whitespace and check for "=".
- int wsStart = _offset;
- var ws = _readWhile(_isWhitespace);
-
- if (_peek() == _EQUAL) {
- _parseChangeDelimiterTag(start);
-
- } else {
- // Scan standard mustache tag.
- var value = new String.fromCharCodes(_openDelimiterInner == null
- ? [_openDelimiter]
- : [_openDelimiter, _openDelimiterInner]);
-
- _push(TokenType.openDelimiter, value, start, wsStart);
-
- if (ws != '') _push(TokenType.whitespace, ws, wsStart, _offset);
-
- _scanTagContent();
- _scanCloseDelimiter();
- }
- }
- }
- return _tokens;
- }
-
- int _peek() => _c;
-
- int _read() {
- int c = _c;
- _offset++;
- _c = _itr.moveNext() ? _itr.current : _EOF;
- return c;
- }
-
- String _readWhile(bool test(int charCode)) {
- int start = _offset;
- while (_peek() != _EOF && test(_peek())) {
- _read();
- }
- int end = _peek() == _EOF ? _source.length : _offset;
- return _source.substring(start, end);
- }
-
- _expect(int expectedCharCode) {
- int c = _read();
-
- if (c == _EOF) {
- throw new TemplateException('Unexpected end of input',
- _templateName, _source, _offset);
-
- } else if (c != expectedCharCode) {
- throw new TemplateException('Unexpected character, '
- 'expected: ${new String.fromCharCode(expectedCharCode)}, '
- 'was: ${new String.fromCharCode(c)}', _templateName, _source, _offset);
- }
- }
-
- // TODO rename this.
- _push(TokenType type, String value, int start, int end) =>
- _tokens.add(new Token(type, value, start, end));
-
- bool _isWhitespace(int c)
- => const [_SPACE, _TAB , _NEWLINE, _RETURN].contains(c);
-
- // Scan text. This adds text tokens, line end tokens, and whitespace
- // tokens for whitespace at the begining of a line. This is because the
- // mustache spec requires special handing of whitespace.
- void _scanText() {
- int start = 0;
- TokenType token;
- String value;
-
- for (int c = _peek(); c != _EOF && c != _openDelimiter; c = _peek()) {
- start = _offset;
-
- switch (c) {
- case _SPACE:
- case _TAB:
- value = _readWhile((c) => c == _SPACE || c == _TAB);
- token = TokenType.whitespace;
- break;
-
- case _NEWLINE:
- _read();
- token = TokenType.lineEnd;
- value = '\n';
- break;
-
- case _RETURN:
- _read();
- if (_peek() == _NEWLINE) {
- _read();
- token = TokenType.lineEnd;
- value = '\r\n';
- } else {
- token = TokenType.text;
- value = '\r';
- }
- break;
-
- default:
- value = _readWhile((c) => c != _openDelimiter && c != _NEWLINE);
- token = TokenType.text;
- }
-
- _push(token, value, start, _offset);
- }
- }
-
- // Scan contents of a tag and the end delimiter token.
- void _scanTagContent() {
-
- int start;
- TokenType token;
- String value;
- List<Token> result = <Token>[];
-
- bool isCloseDelimiter(int c) =>
- (_closeDelimiterInner == null && c == _closeDelimiter)
- || (_closeDelimiterInner != null && c == _closeDelimiterInner);
-
- for (int c = _peek(); c != _EOF && !isCloseDelimiter(c); c = _peek()) {
-
- start = _offset;
-
- switch (c) {
- case _HASH:
- case _CARET:
- case _FORWARD_SLASH:
- case _GT:
- case _AMP:
- case _EXCLAIM:
- _read();
- token = TokenType.sigil;
- value = new String.fromCharCode(c);
- break;
-
- case _SPACE:
- case _TAB:
- case _NEWLINE:
- case _RETURN:
- token = TokenType.whitespace;
- value = _readWhile(_isWhitespace);
- break;
-
- case _PERIOD:
- _read();
- token = TokenType.dot;
- value = '.';
- break;
-
- default:
- // Indentifier can be any other character in lenient mode.
- token = TokenType.identifier;
- value = _readWhile((c) => !(const [ _HASH, _CARET, _FORWARD_SLASH,
- _GT, _AMP, _EXCLAIM, _EQUAL, _SPACE, _TAB, _NEWLINE, _RETURN,
- _PERIOD].contains(c)) &&
- c != _closeDelimiterInner &&
- c != _closeDelimiter);
- }
- _push(token, value, start, _offset);
- }
- }
-
- // Scan close delimiter token.
- void _scanCloseDelimiter() {
-
- if (_peek() != _EOF) {
- int start = _offset;
-
- if (_closeDelimiterInner != null) _expect(_closeDelimiterInner);
- _expect(_closeDelimiter);
-
- String value = new String.fromCharCodes(_closeDelimiterInner == null
- ? [_closeDelimiter]
- : [_closeDelimiterInner, _closeDelimiter]);
-
- _push(TokenType.closeDelimiter, value, start, _offset);
- }
- }
-
- // Scan close triple mustache delimiter token.
- void _scanCloseTripleMustache() {
- if (_peek() != _EOF) {
- int start = _offset;
-
- _expect(_CLOSE_MUSTACHE);
- _expect(_CLOSE_MUSTACHE);
- _expect(_CLOSE_MUSTACHE);
-
- _push(TokenType.closeDelimiter, '}}}', start, _offset);
- }
- }
-
- // Open delimiter characters and = have already been read.
- void _parseChangeDelimiterTag(int start) {
-
- _expect(_EQUAL);
-
- var delimiterInner = _closeDelimiterInner;
- var delimiter = _closeDelimiter;
-
- _readWhile(_isWhitespace);
-
- int c;
- c = _read();
-
- if (c == _EQUAL) throw _error('Incorrect change delimiter tag.');
- _openDelimiter = c;
-
- c = _read();
- if (_isWhitespace(c)) {
- _openDelimiterInner = null;
- } else {
- _openDelimiterInner = c;
- }
-
- _readWhile(_isWhitespace);
-
- c = _read();
-
- if (_isWhitespace(c) || c == _EQUAL)
- throw _error('Incorrect change delimiter tag.');
-
- if (_isWhitespace(_peek()) || _peek() == _EQUAL) {
- _closeDelimiterInner = null;
- _closeDelimiter = c;
- } else {
- _closeDelimiterInner = c;
- _closeDelimiter = _read();
- }
-
- _readWhile(_isWhitespace);
-
- _expect(_EQUAL);
-
- _readWhile(_isWhitespace);
-
- if (delimiterInner != null) _expect(delimiterInner);
- _expect(delimiter);
-
- var value = _delimiterString(
- _openDelimiter,
- _openDelimiterInner,
- _closeDelimiterInner,
- _closeDelimiter);
-
- _push(TokenType.changeDelimiter, value, start, _offset);
- }
-
- TemplateException _error(String message) {
- return new TemplateException(message, _templateName, _source, _offset);
- }
-
-}
-
-_delimiterString(int open, int openInner, int closeInner, int close) {
- var buffer = new StringBuffer();
- buffer.writeCharCode(open);
- if (openInner != null) buffer.writeCharCode(openInner);
- buffer.write(' ');
- if (closeInner != null) buffer.writeCharCode(closeInner);
- buffer.writeCharCode(close);
- return buffer.toString();
-}
-
-List<int> _parseDelimiterString(String s) {
- if (s == null) return [_OPEN_MUSTACHE, _OPEN_MUSTACHE,
- _CLOSE_MUSTACHE, _CLOSE_MUSTACHE];
- if (s.length == 3) {
- return [s.codeUnits[0], null, null, s.codeUnits[2]];
-
- } else if (s.length == 5) {
- return [s.codeUnits[0],
- s.codeUnits[1],
- s.codeUnits[3],
- s.codeUnits[4]];
- } else {
- throw new TemplateException(
- 'Invalid delimiter string $s', null, null, null);
- }
-}
-
-const int _EOF = -1;
-const int _TAB = 9;
-const int _NEWLINE = 10;
-const int _RETURN = 13;
-const int _SPACE = 32;
-const int _EXCLAIM = 33;
-const int _QUOTE = 34;
-const int _APOS = 39;
-const int _HASH = 35;
-const int _AMP = 38;
-const int _PERIOD = 46;
-const int _FORWARD_SLASH = 47;
-const int _LT = 60;
-const int _EQUAL = 61;
-const int _GT = 62;
-const int _CARET = 94;
-
-const int _OPEN_MUSTACHE = 123;
-const int _CLOSE_MUSTACHE = 125;
-
-const int _A = 65;
-const int _Z = 90;
-const int _a = 97;
-const int _z = 122;
-const int _0 = 48;
-const int _9 = 57;
-
-const int _UNDERSCORE = 95;
-const int _MINUS = 45;
-
-
diff --git a/lib/src/template.dart b/lib/src/template.dart
index 8ea5ed3..ea5ad61 100644
--- a/lib/src/template.dart
+++ b/lib/src/template.dart
@@ -1,4 +1,10 @@
-part of mustache.impl;
+library mustache.template;
+
+import 'package:mustache/mustache.dart' as m;
+
+import 'node.dart';
+import 'parser.dart' as parser;
+import 'render_context.dart';
class Template implements m.Template {
@@ -21,6 +27,9 @@
final String _name;
final m.PartialResolver _partialResolver;
+ //TODO get rid of this. Only needed for rendering partials.
+ List<Node> getNodes() => _nodes;
+
String get name => _name;
String renderString(values) {
@@ -35,112 +44,3 @@
renderWithContext(ctx, _nodes);
}
}
-
-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/token.dart b/lib/src/token.dart
index 9e9b88f..682bc89 100644
--- a/lib/src/token.dart
+++ b/lib/src/token.dart
@@ -1,46 +1,49 @@
-part of mustache.impl;
+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, {this.indent : ''});
+ Token(this.type, this.value, this.start, this.end);
- final int type;
+ final TokenType type;
final String value;
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;
+ String toString() => "(Token ${type.name} \"$value\" $start $end)";
- toString() => "${_tokenTypeString(type)}: "
- "\"${value.replaceAll('\n', '\\n')}\"";
+ // 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.
}
-
-//FIXME use enums
-const int _TEXT = 1;
-const int _VARIABLE = 2;
-const int _PARTIAL = 3;
-const int _OPEN_SECTION = 4;
-const int _OPEN_INV_SECTION = 5;
-const int _CLOSE_SECTION = 6;
-const int _COMMENT = 7;
-const int _UNESC_VARIABLE = 8;
-const int _WHITESPACE = 9; // Should be filtered out, before returned by scan.
-const int _LINE_END = 10; // Should be filtered out, before returned by scan.
-const int _CHANGE_DELIMITER = 11;
-
-_tokenTypeString(int type) => [
- '?',
- 'Text',
- 'Var',
- 'Par',
- 'Open',
- 'OpenInv',
- 'Close',
- 'Comment',
- 'UnescVar',
- 'Whitespace',
- 'LineEnd',
- 'ChangeDelimiter'][type];
diff --git a/lib/src/token2.dart b/lib/src/token2.dart
deleted file mode 100644
index 682bc89..0000000
--- a/lib/src/token2.dart
+++ /dev/null
@@ -1,49 +0,0 @@
-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/mustache_test.dart b/test/mustache_test.dart
index 055f4fe..d55c5be 100644
--- a/test/mustache_test.dart
+++ b/test/mustache_test.dart
@@ -7,7 +7,7 @@
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 = 'Unless in lenient mode tags may only contain';
+const BAD_TAG_NAME = 'Unless in lenient mode, tags may only contain';
const VALUE_NULL = 'Value was null or missing';
const VALUE_MISSING = 'Value was missing';
const UNCLOSED_TAG = 'Unclosed tag';
@@ -279,12 +279,13 @@
test('Unexpected EOF', () {
var source = '{{#section}}_{{var}}_{{/section';
var ex = renderFail(source, {"section": {"var": "bob"}});
- expectFail(ex, 1, source.length, UNEXPECTED_EOF);
+ expectFail(ex, 1, 22, UNEXPECTED_EOF);
});
test('Bad tag name, open section', () {
var source = r'{{#section$%$^%}}_{{var}}_{{/section}}';
var ex = renderFail(source, {"section": {"var": "bob"}});
+ print(ex);
expectFail(ex, null, null, BAD_TAG_NAME);
});
diff --git a/test/parser_test.dart b/test/parser_test.dart
index a1c5495..9096369 100644
--- a/test/parser_test.dart
+++ b/test/parser_test.dart
@@ -1,9 +1,9 @@
import 'package:unittest/unittest.dart';
-import 'package:mustache/src/mustache_impl.dart' show TextNode, VariableNode, SectionNode, PartialNode;
+import 'package:mustache/src/node.dart';
import 'package:mustache/src/parser.dart';
-import 'package:mustache/src/scanner2.dart';
-import 'package:mustache/src/token2.dart';
+import 'package:mustache/src/scanner.dart';
+import 'package:mustache/src/token.dart';
main() {