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('') + ..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'),