diff --git a/.idea/runConfigurations/for_loop_in_render_test_dart.xml b/.idea/runConfigurations/for_loop_in_render_test_dart.xml
new file mode 100644
index 00000000..de242953
--- /dev/null
+++ b/.idea/runConfigurations/for_loop_in_render_test_dart.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/jael__example.xml b/.idea/runConfigurations/jael__example.xml
new file mode 100644
index 00000000..5b6d4161
--- /dev/null
+++ b/.idea/runConfigurations/jael__example.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/jael/.gitignore b/jael/.gitignore
index 4353a22c..cab9622e 100644
--- a/jael/.gitignore
+++ b/jael/.gitignore
@@ -12,3 +12,5 @@ pubspec.lock
# Directory created by dartdoc
# If you don't generate documentation locally you can remove this line.
doc/api/
+
+.dart_tool
\ No newline at end of file
diff --git a/jael/CHANGELOG.md b/jael/CHANGELOG.md
new file mode 100644
index 00000000..a87c9287
--- /dev/null
+++ b/jael/CHANGELOG.md
@@ -0,0 +1,4 @@
+# 1.0.1
+* Reworked the scanner; thereby fixing an extremely pesky bug
+that prevented successful parsing of Jael files containing
+JavaScript.
\ No newline at end of file
diff --git a/jael/README.md b/jael/README.md
index 97759a8b..6c287e9f 100644
--- a/jael/README.md
+++ b/jael/README.md
@@ -11,7 +11,7 @@ In your `pubspec.yaml`:
```yaml
dependencies:
- jael: ^1.0.0-beta
+ jael: ^1.0.0
```
# API
diff --git a/jael/example/main.dart b/jael/example/main.dart
new file mode 100644
index 00000000..f78e3c93
--- /dev/null
+++ b/jael/example/main.dart
@@ -0,0 +1,37 @@
+import 'dart:io';
+import 'package:charcode/charcode.dart';
+import 'package:code_buffer/code_buffer.dart';
+import 'package:jael/jael.dart' as jael;
+import 'package:symbol_table/symbol_table.dart';
+
+main() {
+ while (true) {
+ var buf = new StringBuffer();
+ int ch;
+ print('Enter lines of Jael text, terminated by CTRL^D.');
+ print('All environment variables are injected into the template scope.');
+
+ while ((ch = stdin.readByteSync()) != $eot && ch != -1) {
+ buf.writeCharCode(ch);
+ }
+
+ var document = jael.parseDocument(
+ buf.toString(),
+ sourceUrl: 'stdin',
+ onError: stderr.writeln,
+ );
+
+ if (document == null) {
+ stderr.writeln('Could not parse the given text.');
+ } else {
+ var output = new CodeBuffer();
+ const jael.Renderer().render(
+ document,
+ output,
+ new SymbolTable(values: Platform.environment),
+ strictResolution: false,
+ );
+ print('GENERATED HTML:\n$output');
+ }
+ }
+}
diff --git a/jael/lib/src/ast/token.dart b/jael/lib/src/ast/token.dart
index af629328..4fddbdfe 100644
--- a/jael/lib/src/ast/token.dart
+++ b/jael/lib/src/ast/token.dart
@@ -3,8 +3,9 @@ import 'package:source_span/source_span.dart';
class Token {
final TokenType type;
final FileSpan span;
+ final Match match;
- Token(this.type, this.span);
+ Token(this.type, this.span, this.match);
@override
String toString() {
diff --git a/jael/lib/src/renderer.dart b/jael/lib/src/renderer.dart
index 011535ec..e64e649f 100644
--- a/jael/lib/src/renderer.dart
+++ b/jael/lib/src/renderer.dart
@@ -10,6 +10,8 @@ Document parseDocument(String text,
{sourceUrl, void onError(JaelError error)}) {
var scanner = scan(text, sourceUrl: sourceUrl);
+ //scanner.tokens.forEach(print);
+
if (scanner.errors.isNotEmpty && onError != null)
scanner.errors.forEach(onError);
else if (scanner.errors.isNotEmpty) throw scanner.errors.first;
diff --git a/jael/lib/src/text/parser.dart b/jael/lib/src/text/parser.dart
index f9d0c524..52ae9aa0 100644
--- a/jael/lib/src/text/parser.dart
+++ b/jael/lib/src/text/parser.dart
@@ -260,7 +260,7 @@ class Parser {
if (tagName2.name != tagName.name) {
errors.add(new JaelError(
JaelErrorSeverity.error,
- 'Mismatched closing tags. Expected "${tagName.span}"; got "${tagName2.name}" instead.',
+ 'Mismatched closing tags. Expected "${tagName.span.text}"; got "${tagName2.name}" instead.',
lt2.span));
return null;
}
diff --git a/jael/lib/src/text/scanner.dart b/jael/lib/src/text/scanner.dart
index 8df86d9c..e9ccd002 100644
--- a/jael/lib/src/text/scanner.dart
+++ b/jael/lib/src/text/scanner.dart
@@ -1,10 +1,12 @@
+import 'dart:collection';
import 'package:charcode/ascii.dart';
import 'package:string_scanner/string_scanner.dart';
import '../ast/ast.dart';
final RegExp _whitespace = new RegExp(r'[ \n\r\t]+');
-final RegExp _id = new RegExp(r'(([A-Za-z][A-Za-z0-9_]*-)*([A-Za-z][A-Za-z0-9_]*))');
+final RegExp _id =
+ new RegExp(r'(([A-Za-z][A-Za-z0-9_]*-)*([A-Za-z][A-Za-z0-9_]*))');
final RegExp _string1 = new RegExp(
r"'((\\(['\\/bfnrt]|(u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])))|([^'\\]))*'");
final RegExp _string2 = new RegExp(
@@ -18,7 +20,8 @@ abstract class Scanner {
List get tokens;
}
-final Map _htmlPatterns = {
+final Map _expressionPatterns = {
+//final Map _htmlPatterns = {
'{{': TokenType.doubleCurlyL,
'{{-': TokenType.doubleCurlyL,
@@ -34,9 +37,9 @@ final Map _htmlPatterns = {
_string1: TokenType.string,
_string2: TokenType.string,
_id: TokenType.id,
-};
+//};
-final Map _expressionPatterns = {
+//final Map _expressionPatterns = {
'}}': TokenType.doubleCurlyR,
// Keywords
@@ -78,6 +81,8 @@ final Map _expressionPatterns = {
class _Scanner implements Scanner {
final List errors = [];
final List tokens = [];
+ _ScannerState state = _ScannerState.html;
+ final Queue openTags = new Queue();
SpanScanner _scanner;
@@ -85,121 +90,153 @@ class _Scanner implements Scanner {
_scanner = new SpanScanner(text, sourceUrl: sourceUrl);
}
- Token _scanFrom(Map patterns,
- [LineScannerState textStart]) {
- var potential = [];
-
- patterns.forEach((pattern, type) {
- if (_scanner.matches(pattern))
- potential.add(new Token(type, _scanner.lastSpan));
- });
-
- if (potential.isEmpty) return null;
-
- if (textStart != null) {
- var span = _scanner.spanFrom(textStart);
- tokens.add(new Token(TokenType.text, span));
- }
-
- potential.sort((a, b) => b.span.length.compareTo(a.span.length));
-
- var token = potential.first;
- tokens.add(token);
-
- _scanner.scan(token.span.text);
-
- return token;
- }
-
void scan() {
- while (!_scanner.isDone) scanHtmlTokens();
- }
-
- void scanHtmlTokens() {
- LineScannerState textStart;
-
while (!_scanner.isDone) {
- var state = _scanner.state;
+ if (state == _ScannerState.html) {
+ scanHtml();
+ } else if (state == _ScannerState.freeText) {
+ // Just keep parsing until we hit ""
+ var start = _scanner.state, end = start;
- // Skip whitespace conditionally
- if (textStart == null) {
- _scanner.scan(_whitespace);
- }
+ while (!_scanner.isDone) {
+ // Break on {{
+ if (_scanner.matches('{{')) {
+ state = _ScannerState.html;
+ //_scanner.position--;
+ break;
+ }
- var lastToken = _scanFrom(_htmlPatterns, textStart);
+ var ch = _scanner.readChar();
- if (lastToken?.type == TokenType.gt) {
- if (!_scanner.isDone) {
- var state = _scanner.state;
- var ch = _scanner.peekChar();
+ if (ch == $lt && !_scanner.isDone) {
+ if (_scanner.matches('/')) {
+ // If we reached "", backtrack and break into HTML
- if (ch != $space && ch != $cr && ch != $lf && ch != $tab) {
- while (!_scanner.isDone) {
- var ch = _scanner.peekChar();
+ // Also break when we reach = 2 &&
- tokens[tokens.length - 2].type == TokenType.gt) {
- // Fold in the ID into a text node...
- tokens.removeLast();
- textStart = state;
- } else if ((lastToken?.type == TokenType.id ||
- lastToken?.type == TokenType.string) &&
- tokens.length >= 2 &&
- tokens[tokens.length - 2].type == TokenType.text) {
- // Append the ID/string into the old text node
- tokens.removeLast();
- tokens.removeLast();
-
- // Not sure how, but the following logic seems to occur
- // automatically:
- //
- //var textToken = tokens.removeLast();
- //var newSpan = textToken.span.expand(lastToken.span);
- //tokens.add(new Token(TokenType.text, newSpan));
- } else if (lastToken != null) {
- textStart = null;
- } else if (!_scanner.isDone ?? lastToken == null) {
- textStart ??= state;
- _scanner.readChar();
- }
- }
-
- if (textStart != null) {
- var span = _scanner.spanFrom(textStart);
- tokens.add(new Token(TokenType.text, span));
}
}
- void scanExpressionTokens([bool allowGt = false]) {
- Token lastToken;
+ void scanHtml() {
+ var brackets = new Queue();
do {
- _scanner.scan(_whitespace);
- lastToken = _scanFrom(_expressionPatterns);
- } while (!_scanner.isDone &&
- lastToken != null &&
- lastToken.type != TokenType.doubleCurlyR &&
- (allowGt || lastToken.type != TokenType.gt));
+ // Only continue if we find a left bracket
+ if (true) {// || _scanner.matches('<') || _scanner.matches('{{')) {
+ var potential = [];
+
+ while (true) {
+ // Scan whitespace
+ _scanner.scan(_whitespace);
+
+ _expressionPatterns.forEach((pattern, type) {
+ if (_scanner.matches(pattern))
+ potential
+ .add(new Token(type, _scanner.lastSpan, _scanner.lastMatch));
+ });
+
+ potential.sort((a, b) => b.span.length.compareTo(a.span.length));
+
+ if (potential.isEmpty) break;
+
+ var token = potential.first;
+ tokens.add(token);
+
+ _scanner.scan(token.span.text);
+
+ if (token.type == TokenType.lt) {
+ brackets.addFirst(token);
+
+ // Try to see if we are at a tag.
+ var replay = _scanner.state;
+ _scanner.scan(_whitespace);
+
+ if (_scanner.matches(_id)) {
+ openTags.addFirst(_scanner.lastMatch[0]);
+ } else {
+ _scanner.state = replay;
+ }
+ } else if (token.type == TokenType.slash) {
+ // Only push if we're at , or foo/>
+
+ if (brackets.isNotEmpty && brackets.first.type == TokenType.slash)
+ brackets.removeFirst();
+ //else if (_scanner.matches('>')) brackets.removeFirst();
+ else if (brackets.isNotEmpty &&
+ brackets.first.type == TokenType.lt) {
+ // We're at foo>, try to parse text?
+ brackets.removeFirst();
+
+ var replay = _scanner.state;
+ _scanner.scan(_whitespace);
+
+ if (!_scanner.matches('<')) {
+ _scanner.state = replay;
+ state = _ScannerState.freeText;
+ break;
+ }
+ }
+ } else if (token.type == TokenType.doubleCurlyR) {
+ state = _ScannerState.freeText;
+ break;
+ }
+
+ potential.clear();
+ }
+ }
+ } while (brackets.isNotEmpty && !_scanner.isDone);
+
+ state = _ScannerState.freeText;
}
}
+
+enum _ScannerState { html, freeText }
diff --git a/jael/pubspec.yaml b/jael/pubspec.yaml
index f831741e..9e4d6d96 100644
--- a/jael/pubspec.yaml
+++ b/jael/pubspec.yaml
@@ -1,10 +1,10 @@
name: jael
-version: 1.0.0
+version: 1.0.1
description: A simple server-side HTML templating engine for Dart.
author: Tobe O
homepage: https://github.com/angel-dart/jael/tree/master/jael
environment:
- sdk: ">=1.19.0"
+ sdk: ">=1.19.0 <=2.0.0"
dependencies:
charcode: ^1.0.0
code_buffer: ^1.0.0
diff --git a/jael/test/render/render_test.dart b/jael/test/render/render_test.dart
index 4022f16e..bd0e9345 100644
--- a/jael/test/render/render_test.dart
+++ b/jael/test/render/render_test.dart
@@ -27,7 +27,7 @@ main() {
'avatar': 'thosakwe.png',
}
});
- } on jael.JaelError catch(e) {
+ } on jael.JaelError catch (e) {
print(e);
print(e.stackTrace);
}
@@ -75,7 +75,7 @@ main() {
print(buf);
expect(
- buf.toString(),
+ buf.toString().replaceAll('\n', '').replaceAll(' ', '').trim(),
'''
@@ -84,10 +84,12 @@ main() {
Pokémon
Darkrai - Dark
-
+