From 3402e5a0db360d94ce9ecbf4a75ad7362640f2eb Mon Sep 17 00:00:00 2001 From: Tobe O Date: Tue, 26 Jun 2018 11:29:03 -0400 Subject: [PATCH] Added DSX syntax to Jael --- .../tests_in_dsx_test_dart.xml | 7 +++ jael.iml | 1 + jael/README.md | 2 +- jael/lib/src/ast/element.dart | 5 +++ jael/lib/src/ast/token.dart | 4 +- jael/lib/src/renderer.dart | 6 +-- jael/lib/src/text/parser.dart | 43 ++++++++++++++----- jael/lib/src/text/scanner.dart | 22 +++++----- jael/test/render/dsx_test.dart | 43 +++++++++++++++++++ jael/test/text/scan_test.dart | 4 +- 10 files changed, 109 insertions(+), 28 deletions(-) create mode 100644 .idea/runConfigurations/tests_in_dsx_test_dart.xml create mode 100644 jael/test/render/dsx_test.dart diff --git a/.idea/runConfigurations/tests_in_dsx_test_dart.xml b/.idea/runConfigurations/tests_in_dsx_test_dart.xml new file mode 100644 index 00000000..9c1ec0de --- /dev/null +++ b/.idea/runConfigurations/tests_in_dsx_test_dart.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/jael.iml b/jael.iml index cef8c4b3..449f16ce 100644 --- a/jael.iml +++ b/jael.iml @@ -13,6 +13,7 @@ + diff --git a/jael/README.md b/jael/README.md index 6c287e9f..2e78c2a9 100644 --- a/jael/README.md +++ b/jael/README.md @@ -34,7 +34,7 @@ void myFunction() { '''; var buf = new CodeBuffer(); - var document = jael.parseDocument(template, sourceUrl: 'test.jl'); + var document = jael.parseDocument(template, sourceUrl: 'test.jl', asDSX: false); var scope = new SymbolTable(values: { 'profile': { 'avatar': 'thosakwe.png', diff --git a/jael/lib/src/ast/element.dart b/jael/lib/src/ast/element.dart index f9200db5..a8b8c2a0 100644 --- a/jael/lib/src/ast/element.dart +++ b/jael/lib/src/ast/element.dart @@ -33,8 +33,13 @@ abstract class Element extends ElementChild { ]; Identifier get tagName; + Iterable get attributes; + Iterable get children; + + Attribute getAttribute(String name) => + attributes.firstWhere((a) => a.name == name, orElse: () => null); } class SelfClosingElement extends Element { diff --git a/jael/lib/src/ast/token.dart b/jael/lib/src/ast/token.dart index 57c95308..0e4d4a04 100644 --- a/jael/lib/src/ast/token.dart +++ b/jael/lib/src/ast/token.dart @@ -34,8 +34,8 @@ enum TokenType { */ lBracket, rBracket, - doubleCurlyL, - doubleCurlyR, + lDoubleCurly, + rDoubleCurly, lCurly, rCurly, lParen, diff --git a/jael/lib/src/renderer.dart b/jael/lib/src/renderer.dart index aa4a3870..a7299eb5 100644 --- a/jael/lib/src/renderer.dart +++ b/jael/lib/src/renderer.dart @@ -7,8 +7,8 @@ import 'text/scanner.dart'; /// Parses a Jael document. Document parseDocument(String text, - {sourceUrl, void onError(JaelError error)}) { - var scanner = scan(text, sourceUrl: sourceUrl); + {sourceUrl, bool asDSX: false, void onError(JaelError error)}) { + var scanner = scan(text, sourceUrl: sourceUrl, asDSX: asDSX); //scanner.tokens.forEach(print); @@ -16,7 +16,7 @@ Document parseDocument(String text, scanner.errors.forEach(onError); else if (scanner.errors.isNotEmpty) throw scanner.errors.first; - var parser = new Parser(scanner); + var parser = new Parser(scanner, asDSX: asDSX); var doc = parser.parseDocument(); if (parser.errors.isNotEmpty && onError != null) diff --git a/jael/lib/src/text/parser.dart b/jael/lib/src/text/parser.dart index 52ae9aa0..3a162072 100644 --- a/jael/lib/src/text/parser.dart +++ b/jael/lib/src/text/parser.dart @@ -5,11 +5,12 @@ import 'scanner.dart'; class Parser { final List errors = []; final Scanner scanner; + final bool asDSX; Token _current; int _index = -1; - Parser(this.scanner); + Parser(this.scanner, {this.asDSX: false}); Token get current => _current; @@ -148,7 +149,7 @@ class Parser { Text parseText() => next(TokenType.text) ? new Text(_current) : null; Interpolation parseInterpolation() { - if (!next(TokenType.doubleCurlyL)) return null; + if (!next(asDSX ? TokenType.lCurly : TokenType.lDoubleCurly)) return null; var doubleCurlyL = _current; var expression = parseExpression(0); @@ -159,9 +160,10 @@ class Parser { return null; } - if (!next(TokenType.doubleCurlyR)) { + if (!next(asDSX ? TokenType.rCurly : TokenType.rDoubleCurly)) { + var expected = asDSX ? '}' : '}}'; errors.add(new JaelError(JaelErrorSeverity.error, - 'Missing closing "}}" in interpolation.', expression.span)); + 'Missing closing "$expected" in interpolation.', expression.span)); return null; } @@ -260,7 +262,8 @@ class Parser { if (tagName2.name != tagName.name) { errors.add(new JaelError( JaelErrorSeverity.error, - 'Mismatched closing tags. Expected "${tagName.span.text}"; got "${tagName2.name}" instead.', + 'Mismatched closing tags. Expected "${tagName.span + .text}"; got "${tagName2.name}" instead.', lt2.span)); return null; } @@ -291,21 +294,41 @@ class Parser { if (next(TokenType.equals)) { equals = _current; - } else if (next(TokenType.nequ)) { + } else if (!asDSX && next(TokenType.nequ)) { nequ = _current; } else { return new Attribute(id, string, null, null, null); } - var value = parseExpression(0); + if (!asDSX) { + var value = parseExpression(0); + + if (value == null) { + errors.add(new JaelError(JaelErrorSeverity.error, + 'Missing expression in attribute.', equals?.span ?? nequ.span)); + return null; + } + + return new Attribute(id, string, equals, nequ, value); + } else { + // Find either a string, or an interpolation. + var value = implicitString(); + + if (value != null) { + return new Attribute(id, string, equals, nequ, value); + } + + var interpolation = parseInterpolation(); + + if (interpolation != null) { + return new Attribute( + id, string, equals, nequ, interpolation.expression); + } - if (value == null) { errors.add(new JaelError(JaelErrorSeverity.error, 'Missing expression in attribute.', equals?.span ?? nequ.span)); return null; } - - return new Attribute(id, string, equals, nequ, value); } Expression parseExpression(int precedence) { diff --git a/jael/lib/src/text/scanner.dart b/jael/lib/src/text/scanner.dart index c8de1cac..ea8d8ef9 100644 --- a/jael/lib/src/text/scanner.dart +++ b/jael/lib/src/text/scanner.dart @@ -12,7 +12,8 @@ final RegExp _string1 = new RegExp( final RegExp _string2 = new RegExp( r'"((\\(["\\/bfnrt]|(u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])))|([^"\\]))*"'); -Scanner scan(String text, {sourceUrl}) => new _Scanner(text, sourceUrl)..scan(); +Scanner scan(String text, {sourceUrl, bool asDSX: false}) => + new _Scanner(text, sourceUrl)..scan(asDSX: asDSX); abstract class Scanner { List get errors; @@ -24,8 +25,8 @@ final RegExp _htmlComment = new RegExp(r''); final Map _expressionPatterns = { //final Map _htmlPatterns = { - '{{': TokenType.doubleCurlyL, - '{{-': TokenType.doubleCurlyL, + '{{': TokenType.lDoubleCurly, + '{{-': TokenType.lDoubleCurly, // _htmlComment: TokenType.htmlComment, @@ -42,7 +43,7 @@ final Map _expressionPatterns = { //}; //final Map _expressionPatterns = { - '}}': TokenType.doubleCurlyR, + '}}': TokenType.rDoubleCurly, // Keywords 'new': TokenType.$new, @@ -92,10 +93,10 @@ class _Scanner implements Scanner { _scanner = new SpanScanner(text, sourceUrl: sourceUrl); } - void scan() { + void scan({bool asDSX: false}) { while (!_scanner.isDone) { if (state == _ScannerState.html) { - scanHtml(); + scanHtml(asDSX); } else if (state == _ScannerState.freeText) { // Just keep parsing until we hit "(); do { @@ -247,7 +248,8 @@ class _Scanner implements Scanner { break; } } - } else if (token.type == TokenType.doubleCurlyR) { + } else if (token.type == + (asDSX ? TokenType.rCurly : TokenType.rDoubleCurly)) { state = _ScannerState.freeText; break; } diff --git a/jael/test/render/dsx_test.dart b/jael/test/render/dsx_test.dart new file mode 100644 index 00000000..87925998 --- /dev/null +++ b/jael/test/render/dsx_test.dart @@ -0,0 +1,43 @@ +import 'package:jael/jael.dart'; +import 'package:symbol_table/symbol_table.dart'; +import 'package:test/test.dart'; + +void main() { + test('attributes', () { + var doc = parseDSX(''' + + '''); + + var foo = doc.root as SelfClosingElement; + expect(foo.tagName.name, 'foo'); + expect(foo.attributes, hasLength(2)); + expect(foo.getAttribute('bar'), isNotNull); + expect(foo.getAttribute('yes'), isNotNull); + expect(foo.getAttribute('bar').value.compute(null), 'baz'); + expect( + foo + .getAttribute('yes') + .value + .compute(new SymbolTable(values: {'no': 'maybe'})), + 'maybe'); + }); + + test('children', () { + var doc = parseDSX(''' + + {24 * 3} + + '''); + + var bar = doc.root.children.first as RegularElement; + expect(bar.tagName.name, 'bar'); + + var interpolation = bar.children.first as Interpolation; + expect(interpolation.expression.compute(null), 24 * 3); + }); +} + +Document parseDSX(String text) { + return parseDocument(text, + sourceUrl: 'test.dsx', asDSX: true, onError: (e) => throw e); +} diff --git a/jael/test/text/scan_test.dart b/jael/test/text/scan_test.dart index 70bba9c1..080aace7 100644 --- a/jael/test/text/scan_test.dart +++ b/jael/test/text/scan_test.dart @@ -64,13 +64,13 @@ main() { expect(tokens[6], isToken(TokenType.number, '2')); expect(tokens[7], isToken(TokenType.gt)); expect(tokens[8], isToken(TokenType.text, 'three')); - expect(tokens[9], isToken(TokenType.doubleCurlyL)); + expect(tokens[9], isToken(TokenType.lDoubleCurly)); expect(tokens[10], isToken(TokenType.id, 'four')); expect(tokens[11], isToken(TokenType.gt)); expect(tokens[12], isToken(TokenType.id, 'five')); expect(tokens[13], isToken(TokenType.dot)); expect(tokens[14], isToken(TokenType.id, 'six')); - expect(tokens[15], isToken(TokenType.doubleCurlyR)); + expect(tokens[15], isToken(TokenType.rDoubleCurly)); expect(tokens[16], isToken(TokenType.lt)); expect(tokens[17], isToken(TokenType.slash)); expect(tokens[18], isToken(TokenType.id, 'ul'));