Implemented standalone lines (Basic without whitespace handling)
diff --git a/lib/scanner.dart b/lib/scanner.dart
index 9243112..46444d0 100644
--- a/lib/scanner.dart
+++ b/lib/scanner.dart
@@ -1,6 +1,17 @@
part of mustache;
-List<_Token> _scan(String source, bool lenient) => new _Scanner(source).scan();
+//FIXME Temporarily made public for testing.
+//List<_Token> scan(String source, bool lenient) => _scan(source, lenient);
+//List<_Token> trim(List<_Token> tokens) => _trim(tokens);
+
+List<_Token> _scan(String source, bool lenient) //=> _trim(new _Scanner(source).scan());
+{
+ var tokens = new _Scanner(source).scan();
+ //print(tokens);
+ tokens = _trim(tokens);
+ //print(tokens);
+ return tokens;
+}
const int _TEXT = 1;
const int _VARIABLE = 2;
@@ -10,11 +21,27 @@
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.
-tokenTypeString(int type) => ['?', 'Text', 'Var', 'Par', 'Open', 'OpenInv', 'Close', 'Comment', 'UnescVar'][type];
+//FIXME make private
+tokenTypeString(int type) => [
+ '?',
+ 'Text',
+ 'Var',
+ 'Par',
+ 'Open',
+ 'OpenInv',
+ 'Close',
+ 'Comment',
+ 'UnescVar',
+ 'Whitespace',
+ 'LineEnd'][type];
const int _EOF = -1;
+const int _TAB = 9;
const int _NEWLINE = 10;
+const int _SPACE = 32;
const int _EXCLAIM = 33;
const int _QUOTE = 34;
const int _APOS = 39;
@@ -28,6 +55,73 @@
const int _OPEN_MUSTACHE = 123;
const int _CLOSE_MUSTACHE = 125;
+// 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 Consecutive text tokens will also be merged into a single token. (Do in a separate merge func).
+List<_Token> _trim(List<_Token> tokens) {
+ int i = 0;
+ _Token read() { var ret = i < tokens.length ? tokens[i++] : null; /* print('Read: $ret'); */ return ret; }
+ _Token peek() => i < tokens.length ? tokens[i] : null;
+ _Token peek2() => i + 1 < tokens.length ? tokens[i + 1] : null;
+
+ bool isTag(token) =>
+ token != null
+ && (peek().type == _OPEN_SECTION
+ || peek().type == _OPEN_INV_SECTION
+ || peek().type == _CLOSE_SECTION);
+
+ 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() {
+ if (isTag(peek()) && isLineEnd(peek2())) {
+ // Swallow leading whitespace.
+ //if (isWhitespace(peek()))
+ // read();
+
+ // Add tag
+ add(read());
+
+ // Swallow trailing whitespace.
+ //if (isWhitespace(peek()))
+ // read();
+
+ // Swallow line end.
+ if (!isLineEnd(peek()))
+ throw 'boom!';
+
+ read();
+ }
+ }
+
+ // 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.line, t.column));
+ standaloneLineCheck();
+ } else if (t.type == _WHITESPACE) {
+ // Convert whitespace to text token
+ add(new _Token(_TEXT, t.value, t.line, t.column));
+ } else {
+ // Preserve token
+ add(t);
+ }
+ }
+
+ return result;
+}
+
class _Token {
_Token(this.type, this.value, this.line, this.column);
final int type;
@@ -48,10 +142,8 @@
_addStringToken(int type) {
int l = _r.line, c = _r.column;
- var value = _readString();
- if (type != _TEXT && type != _COMMENT) {
- value = value.trim();
- }
+ var value = type == _TEXT ? _readLine() : _readString();
+ if (type != _TEXT && type != _COMMENT) value = value.trim();
_tokens.add(new _Token(type, value, l, c));
}
@@ -76,7 +168,20 @@
}
String _readString() => _r.readWhile(
- (c) => c != _OPEN_MUSTACHE && c != _CLOSE_MUSTACHE && c != _EOF);
+ (c) => c != _OPEN_MUSTACHE
+ && c != _CLOSE_MUSTACHE
+ && c != _EOF);
+
+String _readLine() => _r.readWhile(
+ (c) => c != _OPEN_MUSTACHE
+ && c != _CLOSE_MUSTACHE
+ && c != _EOF
+ && c != _NEWLINE);
+
+ // Actually excludes newlines.
+ String _readWhitespace() => _r.readWhile(
+ (c) => c == _SPACE
+ || c == _TAB);
List<_Token> scan() {
while(true) {
@@ -103,6 +208,15 @@
_read();
_addCharToken(_TEXT, _CLOSE_MUSTACHE);
break;
+ case _NEWLINE:
+ _read();
+ _addCharToken(_LINE_END, _NEWLINE); //TODO handle \r\n
+ break;
+ case _SPACE:
+ case _TAB:
+ var value = _readWhitespace();
+ _tokens.add(new _Token(_WHITESPACE, value, _r.line, _r.column));
+ break;
default:
_addStringToken(_TEXT);
}
diff --git a/lib/template.dart b/lib/template.dart
index 9f86890..7c3ad7b 100644
--- a/lib/template.dart
+++ b/lib/template.dart
@@ -13,7 +13,7 @@
for (var t in tokens) {
if (t.type == _TEXT || t.type == _VARIABLE || t.type == _UNESC_VARIABLE) {
if (t.type == _VARIABLE || t.type == _UNESC_VARIABLE)
- _checkTagChars(t, lenient);
+ _checkTagChars(t, lenient);
stack.last.children.add(new _Node.fromToken(t));
} else if (t.type == _OPEN_SECTION || t.type == _OPEN_INV_SECTION) {
diff --git a/test/mustache_spec_test.dart b/test/mustache_spec_test.dart
index c3d6fa6..44c5e6f 100644
--- a/test/mustache_spec_test.dart
+++ b/test/mustache_spec_test.dart
@@ -49,10 +49,12 @@
var output;
var exception;
+ var trace;
try {
output = mustache.parse(template, lenient: true).renderString(data, lenient: true);
- } catch (ex) {
+ } catch (ex, stacktrace) {
exception = ex;
+ trace = stacktrace;
}
var passed = output == expected;
var result = passed ? 'Pass' : 'Fail';
@@ -63,7 +65,8 @@
print(' Data: ${json.stringify(data)}');
print(' Expected: $expected');
print(' Output: $output');
- if (exception != null) print(' Exception: $exception'); // TODO stack trace.
+ if (exception != null) print(' Exception: $exception');
+ if (trace != null) print(trace);
print('');
print('');
}