diff --git a/.idea/runConfigurations/main_dart.xml b/.idea/runConfigurations/main_dart.xml
new file mode 100644
index 00000000..644077c1
--- /dev/null
+++ b/.idea/runConfigurations/main_dart.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/angel_jael/example/main.dart b/angel_jael/example/main.dart
new file mode 100644
index 00000000..ff044dd4
--- /dev/null
+++ b/angel_jael/example/main.dart
@@ -0,0 +1,27 @@
+import 'package:angel_framework/angel_framework.dart';
+import 'package:angel_jael/angel_jael.dart';
+import 'package:file/local.dart';
+import 'package:logging/logging.dart';
+
+main() async {
+ var app = new Angel();
+ var fileSystem = const LocalFileSystem();
+
+ await app.configure(
+ jael(fileSystem.directory('views')),
+ );
+
+ app.get('/', (res) => res.render('index', {'title': 'ESKETTIT'}));
+
+ app.use(() => throw new AngelHttpException.notFound());
+
+ app.logger = new Logger('angel')
+ ..onRecord.listen((rec) {
+ print(rec);
+ if (rec.error != null) print(rec.error);
+ if (rec.stackTrace != null) print(rec.stackTrace);
+ });
+
+ var server = await app.startServer(null, 3000);
+ print('Listening at http://${server.address.address}:${server.port}');
+}
diff --git a/angel_jael/example/views/index.jl b/angel_jael/example/views/index.jl
new file mode 100644
index 00000000..e7212a43
--- /dev/null
+++ b/angel_jael/example/views/index.jl
@@ -0,0 +1,5 @@
+
+
+ Hello, world!!!
+
+
\ No newline at end of file
diff --git a/angel_jael/example/views/layout.jl b/angel_jael/example/views/layout.jl
new file mode 100644
index 00000000..72ae1ecc
--- /dev/null
+++ b/angel_jael/example/views/layout.jl
@@ -0,0 +1,19 @@
+
+
+
+
+ {{title}}
+
+
+
+ {{title}}
+
+
+ Content goes here.
+
+
+
+
\ No newline at end of file
diff --git a/angel_jael/lib/angel_jael.dart b/angel_jael/lib/angel_jael.dart
new file mode 100644
index 00000000..49264581
--- /dev/null
+++ b/angel_jael/lib/angel_jael.dart
@@ -0,0 +1,87 @@
+import 'dart:convert';
+import 'package:angel_framework/angel_framework.dart';
+import 'package:code_buffer/code_buffer.dart';
+import 'package:file/file.dart';
+import 'package:jael/jael.dart';
+import 'package:jael_preprocessor/jael_preprocessor.dart';
+import 'package:symbol_table/symbol_table.dart';
+
+AngelConfigurer jael(Directory viewsDirectory,
+ {String fileExtension, CodeBuffer createBuffer()}) {
+ fileExtension ??= '.jl';
+ createBuffer ??= () => new CodeBuffer();
+
+ return (Angel app) async {
+ app.viewGenerator = (String name, [Map locals]) async {
+ var file = viewsDirectory.childFile(name + fileExtension);
+ var contents = await file.readAsString();
+ var errors = [];
+ var doc =
+ parseDocument(contents, sourceUrl: file.uri, onError: errors.add);
+ var processed = doc;
+
+ try {
+ processed = await resolve(doc, viewsDirectory, onError: errors.add);
+ } catch (_) {
+ // Ignore these errors, so that we can show syntax errors.
+ }
+
+ var buf = createBuffer();
+ var scope = new SymbolTable(values: locals ?? {});
+
+ if (errors.isEmpty) {
+ try {
+ const Renderer().render(processed, buf, scope);
+ return buf.toString();
+ } on JaelError catch (e) {
+ errors.add(e);
+ }
+ }
+
+ buf
+ ..writeln('')
+ ..writeln('')
+ ..indent()
+ ..writeln('')
+ ..indent()
+ ..writeln(
+ '',
+ )
+ ..writeln('${errors.length} Error(s)')
+ ..outdent()
+ ..writeln('')
+ ..writeln('')
+ ..writeln('${errors.length} Error(s)
')
+ ..writeln('')
+ ..indent();
+
+ for (var error in errors) {
+ var type =
+ error.severity == JaelErrorSeverity.warning ? 'warning' : 'error';
+ buf
+ ..writeln('- ')
+ ..indent()
+ ..writeln(
+ '$type: ${error.span.start.toolString}: ${error.message}')
+ ..writeln('
')
+ ..writeln(
+ '' +
+ HTML_ESCAPE
+ .convert(error.span.highlight(color: false))
+ .replaceAll('\n', '
') +
+ '',
+ )
+ ..outdent()
+ ..writeln(' ');
+ }
+
+ buf
+ ..outdent()
+ ..writeln('
')
+ ..writeln('')
+ ..writeln('');
+
+ return buf.toString();
+ };
+ };
+}
diff --git a/angel_jael/pubspec.yaml b/angel_jael/pubspec.yaml
new file mode 100644
index 00000000..bd720ffe
--- /dev/null
+++ b/angel_jael/pubspec.yaml
@@ -0,0 +1,16 @@
+name: angel_jael
+dependencies:
+ angel_framework: ^1.0.0-dev
+ code_buffer: ^1.0.0
+ file: ^2.0.0
+ jael: ^1.0.0-alpha
+ jael_preprocessor: ^1.0.0-alpha
+ symbol_table: ^1.0.0
+dev_dependencies:
+ angel_test: ^1.1.0-alpha
+ test: ^0.12.0
+dependency_overrides:
+ jael:
+ path: ../jael
+ jael_preprocessor:
+ path: ../jael_preprocessor
\ No newline at end of file
diff --git a/jael.iml b/jael.iml
index 697b81c1..6f2c9ef7 100644
--- a/jael.iml
+++ b/jael.iml
@@ -2,7 +2,10 @@
-
+
+
+
+
diff --git a/jael/lib/src/ast/binary.dart b/jael/lib/src/ast/binary.dart
index d8bef3cb..222f2d62 100644
--- a/jael/lib/src/ast/binary.dart
+++ b/jael/lib/src/ast/binary.dart
@@ -38,7 +38,7 @@ class BinaryExpression extends Expression {
return l ?? r;
default:
throw new UnsupportedError(
- 'Unsupported binary operator: "${operator?.span ?? ""}".');
+ 'Unsupported binary operator: "${operator?.span?.text ?? ""}".');
}
}
diff --git a/jael/lib/src/ast/identifier.dart b/jael/lib/src/ast/identifier.dart
index fdfcbc36..c1794827 100644
--- a/jael/lib/src/ast/identifier.dart
+++ b/jael/lib/src/ast/identifier.dart
@@ -10,10 +10,22 @@ class Identifier extends Expression {
@override
compute(SymbolTable scope) {
- var symbol = scope.resolve(name);
- if (symbol == null)
- throw new ArgumentError('The name "$name" does not exist in this scope.');
- return scope.resolve(name).value;
+ switch(name) {
+ case 'null':
+ return null;
+ case 'true':
+ return true;
+ case 'false':
+ return false;
+ default:
+ var symbol = scope.resolve(name);
+ if (symbol == null)
+ throw new ArgumentError(
+ 'The name "$name" does not exist in this scope.');
+ return scope
+ .resolve(name)
+ .value;
+ }
}
String get name => id.span.text;
diff --git a/jael/lib/src/ast/member.dart b/jael/lib/src/ast/member.dart
index 4423fe3e..01e996bb 100644
--- a/jael/lib/src/ast/member.dart
+++ b/jael/lib/src/ast/member.dart
@@ -7,17 +7,18 @@ import 'token.dart';
class MemberExpression extends Expression {
final Expression expression;
- final Token dot;
+ final Token op;
final Identifier name;
- MemberExpression(this.expression, this.dot, this.name);
+ MemberExpression(this.expression, this.op, this.name);
@override
compute(SymbolTable scope) {
var target = expression.compute(scope);
+ if (op.span.text == '?.' && target == null) return null;
return reflect(target).getField(new Symbol(name.name)).reflectee;
}
@override
- FileSpan get span => expression.span.expand(dot.span).expand(name.span);
+ FileSpan get span => expression.span.expand(op.span).expand(name.span);
}
diff --git a/jael/lib/src/ast/token.dart b/jael/lib/src/ast/token.dart
index 2e2dfd81..2d3ee0a8 100644
--- a/jael/lib/src/ast/token.dart
+++ b/jael/lib/src/ast/token.dart
@@ -47,6 +47,7 @@ enum TokenType {
plus,
minus,
elvis,
+ elvis_dot,
lte,
gte,
equ,
@@ -54,4 +55,5 @@ enum TokenType {
number,
hex,
string,
+ question,
}
\ No newline at end of file
diff --git a/jael/lib/src/renderer.dart b/jael/lib/src/renderer.dart
index 65b45e1b..e5c9c931 100644
--- a/jael/lib/src/renderer.dart
+++ b/jael/lib/src/renderer.dart
@@ -65,7 +65,7 @@ class Renderer {
msg = value.keys.fold(new StringBuffer(), (buf, k) {
var v = value[k];
if (v == null) return buf;
- return buf..write('$k=$v;');
+ return buf..write('$k: $v;');
}).toString();
} else {
msg = value.toString();
diff --git a/jael/lib/src/text/parselet/infix.dart b/jael/lib/src/text/parselet/infix.dart
index 6c80afd1..7ee8b78e 100644
--- a/jael/lib/src/text/parselet/infix.dart
+++ b/jael/lib/src/text/parselet/infix.dart
@@ -2,6 +2,7 @@ part of jael.src.text.parselet;
const Map infixParselets = const {
TokenType.lParen: const CallParselet(),
+ TokenType.elvis_dot: const MemberParselet(),
TokenType.dot: const MemberParselet(),
TokenType.lBracket: const IndexerParselet(),
TokenType.asterisk: const BinaryParselet(14),
diff --git a/jael/lib/src/text/parser.dart b/jael/lib/src/text/parser.dart
index a7ca4041..0f16d853 100644
--- a/jael/lib/src/text/parser.dart
+++ b/jael/lib/src/text/parser.dart
@@ -300,6 +300,16 @@ class Parser {
while (precedence < _nextPrecedence()) {
_current = scanner.tokens[++_index];
+
+ if (_current.type == TokenType.slash && peek()?.type == TokenType.gt) {
+ // Handle `/>`
+ //
+ // Don't register this as an infix expression.
+ // Instead, backtrack, and return the current expression.
+ _index--;
+ return left;
+ }
+
var infix = infixParselets[_current.type];
var newLeft = infix.parse(this, left, _current);
diff --git a/jael/lib/src/text/scanner.dart b/jael/lib/src/text/scanner.dart
index bc54ba2c..f3621289 100644
--- a/jael/lib/src/text/scanner.dart
+++ b/jael/lib/src/text/scanner.dart
@@ -146,9 +146,9 @@ class _Scanner implements Scanner {
// 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));
+ //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) {
diff --git a/jael/test/render/render_test.dart b/jael/test/render/render_test.dart
index a9f306cd..0c00860c 100644
--- a/jael/test/render/render_test.dart
+++ b/jael/test/render/render_test.dart
@@ -9,7 +9,7 @@ main() {
Hello
-
+
''';
@@ -53,6 +53,7 @@ main() {
''';
var buf = new CodeBuffer();
+ //jael.scan(template, sourceUrl: 'test.jl').tokens.forEach(print);
var document = jael.parseDocument(template, sourceUrl: 'test.jl');
var scope = new SymbolTable(values: {
'pokemon': const _Pokemon('Darkrai', 'Dark'),