Initial delimiters implementation
diff --git a/lib/src/scanner.dart b/lib/src/scanner.dart
index 3925188..ea5d296 100644
--- a/lib/src/scanner.dart
+++ b/lib/src/scanner.dart
@@ -13,6 +13,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) => [
'?',
@@ -25,7 +26,8 @@
'Comment',
'UnescVar',
'Whitespace',
- 'LineEnd'][type];
+ 'LineEnd',
+ 'ChangeDelimiter'][type];
const int _EOF = -1;
const int _TAB = 9;
@@ -40,6 +42,7 @@
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;
@@ -148,6 +151,21 @@
_CharReader _r;
List<_Token> _tokens = new List<_Token>();
+ // These can be changed by the change delimiter tag.
+ int _openDelimiter = _OPEN_MUSTACHE;
+ int _openDelimiterInner = _OPEN_MUSTACHE;
+ int _closeDelimiterInner = _CLOSE_MUSTACHE;
+ int _closeDelimiter = _CLOSE_MUSTACHE;
+
+ List<_Token> scan() {
+ while(true) {
+ int c = _peek();
+ if (c == _EOF) return _tokens;
+ else if (c == _openDelimiter) _scanMustacheTag();
+ else _scanText();
+ }
+ }
+
int _read() => _r.read();
int _peek() => _r.peek();
@@ -199,92 +217,139 @@
}
}
+ // FIXME probably need to differentiate between searching for open, or close
+ // delimiter.
String _readString() => _r.readWhile(
- (c) => c != _OPEN_MUSTACHE
- && c != _CLOSE_MUSTACHE
- && c != _EOF);
+ (c) => c != _closeDelimiterInner
+ //FIXME && (_closeDelimiterInner == null && c != _closeDelimiter)
+ && c != _closeDelimiter
+ && c != _openDelimiter
+ && c != _openDelimiterInner
+ && c != _EOF); //FIXME EOF should be error.
+ // FIXME probably need to differentiate between searching for open, or close
+ // delimiter.
String _readLine() => _r.readWhile(
- (c) => c != _OPEN_MUSTACHE
- && c != _CLOSE_MUSTACHE
- && c != _EOF
+ (c) => c != _closeDelimiterInner
+ //FIXME && (_closeDelimiterInner == null && c != _closeDelimiter)
+ && c != _closeDelimiter
+ && c != _openDelimiter
+ && c != _openDelimiterInner
+ && c != _EOF //FIXME EOF should be error.
&& c != _NEWLINE);
- List<_Token> scan() {
- while(true) {
- switch(_peek()) {
- case _EOF:
- return _tokens;
- case _OPEN_MUSTACHE:
- _scanMustacheTag();
- break;
- default:
- _scanText();
- }
- }
- }
-
+ //FIXME unless in lenient mode only allow spaces.
+ String _readTagWhitespace() => _r.readWhile(_isWhitespace);
+
+ bool _isWhitespace(int c)
+ => const [_SPACE, _TAB , _NEWLINE, _RETURN].contains(c);
+
_scanText() {
while(true) {
- switch(_peek()) {
- case _EOF:
- return;
- case _OPEN_MUSTACHE:
- return;
- case _CLOSE_MUSTACHE:
- _read();
- _addCharToken(_TEXT, _CLOSE_MUSTACHE);
- break;
- case _RETURN:
- _read();
- if (_peek() == _NEWLINE) {
- _read();
- _tokens.add(new _Token(_LINE_END, '\r\n', _r.line, _r.column));
- } else {
- _addCharToken(_TEXT, _RETURN);
- }
- break;
- case _NEWLINE:
- _read();
- _addCharToken(_LINE_END, _NEWLINE); //TODO handle \r\n
- break;
- case _SPACE:
- case _TAB:
- var value = _r.readWhile((c) => c == _SPACE || c == _TAB);
- _tokens.add(new _Token(_WHITESPACE, value, _r.line, _r.column));
- break;
- default:
- _addStringToken(_TEXT);
+ int c = _peek();
+
+ if (c == _EOF) {
+ return;
+
+ } else if (c == _openDelimiter) {
+ return;
+
+ } else if (c == _RETURN) {
+ _read();
+ if (_peek() == _NEWLINE) {
+ _read();
+ _tokens.add(new _Token(_LINE_END, '\r\n', _r.line, _r.column));
+ } else {
+ _addCharToken(_TEXT, _RETURN);
+ }
+ } else if (c == _NEWLINE) {
+ _read();
+ _addCharToken(_LINE_END, _NEWLINE);
+
+ } else if (c == _SPACE || c == _TAB) {
+ var value = _r.readWhile((c) => c == _SPACE || c == _TAB);
+ _tokens.add(new _Token(_WHITESPACE, value, _r.line, _r.column));
+
+ //FIXME figure out why this is required
+ } else if (c == _closeDelimiter || c == _closeDelimiterInner) {
+ _read();
+ _addCharToken(_TEXT, c);
+
+ } else {
+ _addStringToken(_TEXT);
}
}
}
-
+
+ _scanChangeDelimiterTag() {
+ // Open delimiter characters have already been read.
+ _expect(_EQUAL);
+
+ int line = _r.line;
+ int col = _r.column;
+
+ var delimiterInner = _closeDelimiterInner;
+ var delimiter = _closeDelimiter;
+
+ _readTagWhitespace();
+
+ int c;
+ c = _r.read();
+
+ if (c == _EQUAL) throw 'syntax error'; //FIXME
+ _openDelimiter = c;
+
+ c = _r.read();
+ if (_isWhitespace(c)) {
+ _openDelimiterInner = null;
+ } else {
+ _openDelimiterInner = c;
+ }
+
+ _readTagWhitespace();
+
+ c = _r.read();
+ _closeDelimiterInner = c;
+
+ var text = _r.readWhile((c) => c != _EQUAL).trim();
+
+ _expect(_EQUAL);
+ _readTagWhitespace();
+
+ _expect(delimiterInner);
+ _expect(delimiter);
+
+ var value = '$_openDelimiter$_openDelimiterInner '
+ '$_closeDelimiterInner$_closeDelimiter';
+ _tokens.add(new _Token(_CHANGE_DELIMITER, value, line, col));
+ }
+
_scanMustacheTag() {
int startOffset = _r.offset;
- _expect(_OPEN_MUSTACHE);
+ _expect(_openDelimiter);
// If just a single mustache, return this as a text token.
//FIXME is this missing a read call to advance ??
- if (_peek() != _OPEN_MUSTACHE) {
- _addCharToken(_TEXT, _OPEN_MUSTACHE);
+ if (_peek() != _openDelimiterInner) {
+ _addCharToken(_TEXT, _openDelimiter);
return;
}
- _expect(_OPEN_MUSTACHE);
+ if (_openDelimiterInner != null) _expect(_openDelimiterInner);
// Escaped text {{{ ... }}}
if (_peek() == _OPEN_MUSTACHE) {
_read();
_addStringToken(_UNESC_VARIABLE);
_expect(_CLOSE_MUSTACHE);
- _expect(_CLOSE_MUSTACHE);
- _expect(_CLOSE_MUSTACHE);
+ _expect(_closeDelimiterInner);
+ _expect(_closeDelimiter);
return;
}
// Skip whitespace at start of tag. i.e. {{ # foo }} {{ / foo }}
- _r.readWhile((c) => const [_SPACE, _TAB , _NEWLINE, _RETURN].contains(c));
+ _readTagWhitespace();
switch(_peek()) {
case _EOF:
@@ -329,14 +394,19 @@
// lambdas.
_tokens.last.offset = startOffset;
break;
+
+ // Change delimiter {{= ... =}}
+ case _EQUAL:
+ _scanChangeDelimiterTag();
+ return;
// Variable {{ ... }}
default:
_addStringToken(_VARIABLE);
}
- _expect(_CLOSE_MUSTACHE);
- _expect(_CLOSE_MUSTACHE);
+ if (_closeDelimiterInner != null) _expect(_closeDelimiterInner);
+ _expect(_closeDelimiter);
// Store source file offset, so source substrings can be extracted for
// lambdas.
diff --git a/lib/src/template.dart b/lib/src/template.dart
index 0ad6ec2..1ce4a6d 100644
--- a/lib/src/template.dart
+++ b/lib/src/template.dart
@@ -35,7 +35,7 @@
stack.removeLast();
- } else if (t.type == _COMMENT) {
+ } else if (t.type == _COMMENT || t.type == _CHANGE_DELIMITER) {
// Do nothing
} else {
@@ -218,6 +218,8 @@
break;
case _COMMENT:
break; // Do nothing.
+ case _CHANGE_DELIMITER:
+ break; // Do nothing
default:
throw new UnimplementedError();
}
diff --git a/test/mustache_specs.dart b/test/mustache_specs.dart
index 314facf..abb9033 100644
--- a/test/mustache_specs.dart
+++ b/test/mustache_specs.dart
@@ -96,7 +96,7 @@
reset () => _callCounter = 0;
}
-Function wrapLambda(Function f) => (LambdaContext ctx) => ctx.renderSource(f(ctx.source));
+Function wrapLambda(Function f) => (LambdaContext ctx) => ctx.renderSource(f(ctx.source).toString());
var lambdas = {
'Interpolation' : wrapLambda((t) => 'world'),