diff --git a/jael/lib/src/ast/ast.dart b/jael/lib/src/ast/ast.dart index 8d32a264..0a963a96 100644 --- a/jael/lib/src/ast/ast.dart +++ b/jael/lib/src/ast/ast.dart @@ -3,6 +3,7 @@ export 'ast_node.dart'; export 'attribute.dart'; export 'binary.dart'; export 'call.dart'; +export 'conditional.dart'; export 'document.dart'; export 'element.dart'; export 'error.dart'; diff --git a/jael/lib/src/ast/conditional.dart b/jael/lib/src/ast/conditional.dart new file mode 100644 index 00000000..871eb4f8 --- /dev/null +++ b/jael/lib/src/ast/conditional.dart @@ -0,0 +1,27 @@ +import 'package:source_span/source_span.dart'; +import 'expression.dart'; +import 'token.dart'; + +class Conditional extends Expression { + final Expression condition, ifTrue, ifFalse; + final Token question, colon; + + Conditional( + this.condition, this.question, this.ifTrue, this.colon, this.ifFalse); + + @override + FileSpan get span { + return condition.span + .expand(question.span) + .expand(ifTrue.span) + .expand(colon.span) + .expand(ifFalse.span); + } + + @override + compute(scope) { + return condition.compute(scope) + ? ifTrue.compute(scope) + : ifFalse.compute(scope); + } +} diff --git a/jael/lib/src/ast/element.dart b/jael/lib/src/ast/element.dart index f9200db5..69bd8be1 100644 --- a/jael/lib/src/ast/element.dart +++ b/jael/lib/src/ast/element.dart @@ -15,6 +15,15 @@ class TextNode extends ElementChild { FileSpan get span => text.span; } +class ScriptTag extends ElementChild { + final Token script_tag; + + ScriptTag(this.script_tag); + + @override + FileSpan get span => script_tag.span; +} + abstract class Element extends ElementChild { static const List selfClosing = const [ 'include', diff --git a/jael/lib/src/ast/token.dart b/jael/lib/src/ast/token.dart index 2d3ee0a8..6c125285 100644 --- a/jael/lib/src/ast/token.dart +++ b/jael/lib/src/ast/token.dart @@ -23,6 +23,7 @@ enum TokenType { slash, equals, id, + script_tag, text, // Keywords diff --git a/jael/lib/src/renderer.dart b/jael/lib/src/renderer.dart index e5c9c931..73711ba3 100644 --- a/jael/lib/src/renderer.dart +++ b/jael/lib/src/renderer.dart @@ -166,6 +166,8 @@ class Renderer { buffer.write(child.span.text.trimRight()); else buffer.write(child.span.text); + } else if (child is ScriptTag) { + buffer.writeln(child.script_tag.span.text); } else if (child is Interpolation) { var value = child.expression.compute(scope); @@ -176,7 +178,7 @@ class Renderer { buffer.write(HTML_ESCAPE.convert(value.toString())); } } else if (child is Element) { - buffer.writeln(); + if (buffer?.lastLine?.text?.isNotEmpty == true) buffer.writeln(); renderElement(child, buffer, scope, html5); } } diff --git a/jael/lib/src/text/parselet/infix.dart b/jael/lib/src/text/parselet/infix.dart index 7ee8b78e..c558845e 100644 --- a/jael/lib/src/text/parselet/infix.dart +++ b/jael/lib/src/text/parselet/infix.dart @@ -16,9 +16,45 @@ const Map infixParselets = const { TokenType.gte: const BinaryParselet(11), TokenType.equ: const BinaryParselet(10), TokenType.nequ: const BinaryParselet(10), + TokenType.question: const ConditionalParselet(), TokenType.equals: const BinaryParselet(3), }; +class ConditionalParselet implements InfixParselet { + @override + int get precedence => 4; + + const ConditionalParselet(); + + @override + Expression parse(Parser parser, Expression left, Token token) { + var ifTrue = parser.parseExpression(0); + + if (ifTrue == null) { + parser.errors.add(new JaelError(JaelErrorSeverity.error, + 'Missing expression in conditional expression.', token.span)); + return null; + } + + if (!parser.next(TokenType.colon)) { + parser.errors.add(new JaelError(JaelErrorSeverity.error, + 'Missing ":" in conditional expression.', ifTrue.span)); + return null; + } + + var colon = parser.current; + var ifFalse = parser.parseExpression(0); + + if (ifFalse == null) { + parser.errors.add(new JaelError(JaelErrorSeverity.error, + 'Missing expression in conditional expression.', colon.span)); + return null; + } + + return new Conditional(left, token, ifTrue, colon, ifFalse); + } +} + class BinaryParselet implements InfixParselet { final int precedence; diff --git a/jael/lib/src/text/parser.dart b/jael/lib/src/text/parser.dart index 0f16d853..bf7dc4cd 100644 --- a/jael/lib/src/text/parser.dart +++ b/jael/lib/src/text/parser.dart @@ -68,9 +68,7 @@ class Parser { StringLiteral implicitString() { if (next(TokenType.string)) { return prefixParselets[TokenType.string].parse(this, _current); - } else if (next(TokenType.text)) { - - } + } else if (next(TokenType.text)) {} return null; } @@ -85,8 +83,10 @@ class Parser { } var doctype = _current, html = parseIdentifier(); if (html?.span?.text?.toLowerCase() != 'html') { - errors.add(new JaelError(JaelErrorSeverity.error, - 'Expected "html" in doctype declaration.', html?.span ?? doctype.span)); + errors.add(new JaelError( + JaelErrorSeverity.error, + 'Expected "html" in doctype declaration.', + html?.span ?? doctype.span)); return null; } @@ -102,8 +102,10 @@ class Parser { } if (public?.span?.text?.toLowerCase() != 'public') { - errors.add(new JaelError(JaelErrorSeverity.error, - 'Expected "public" in doctype declaration.', public?.span ?? html.span)); + errors.add(new JaelError( + JaelErrorSeverity.error, + 'Expected "public" in doctype declaration.', + public?.span ?? html.span)); return null; } @@ -138,6 +140,7 @@ class Parser { parseHtmlComment() ?? parseInterpolation() ?? parseText() ?? + parseScriptTag() ?? parseElement(); HtmlComment parseHtmlComment() => @@ -145,6 +148,9 @@ class Parser { Text parseText() => next(TokenType.text) ? new Text(_current) : null; + ScriptTag parseScriptTag() => + next(TokenType.script_tag) ? new ScriptTag(_current) : null; + Interpolation parseInterpolation() { if (!next(TokenType.doubleCurlyL)) return null; var doubleCurlyL = _current; @@ -301,7 +307,8 @@ class Parser { while (precedence < _nextPrecedence()) { _current = scanner.tokens[++_index]; - if (_current.type == TokenType.slash && peek()?.type == TokenType.gt) { + if (_current.type == TokenType.slash && + peek()?.type == TokenType.gt) { // Handle `/>` // // Don't register this as an infix expression. @@ -314,8 +321,7 @@ class Parser { var newLeft = infix.parse(this, left, _current); if (newLeft == null) { - if (_current.type == TokenType.gt) - _index--; + if (_current.type == TokenType.gt) _index--; return left; } left = newLeft; diff --git a/jael/lib/src/text/scanner.dart b/jael/lib/src/text/scanner.dart index f3621289..264bd415 100644 --- a/jael/lib/src/text/scanner.dart +++ b/jael/lib/src/text/scanner.dart @@ -30,6 +30,7 @@ final Map _htmlPatterns = { '=': TokenType.equals, _string1: TokenType.string, _string2: TokenType.string, + new RegExp(r']*>[^$]*'): TokenType.script_tag, new RegExp(r'([A-Za-z][A-Za-z0-9]*-)*([A-Za-z][A-Za-z0-9]*)'): TokenType.id, }; diff --git a/jael/pubspec.yaml b/jael/pubspec.yaml index c6cd7c1f..31c82148 100644 --- a/jael/pubspec.yaml +++ b/jael/pubspec.yaml @@ -1,5 +1,5 @@ name: jael -version: 1.0.0-alpha+2 +version: 1.0.0-alpha+3 description: A simple server-side HTML templating engine for Dart. author: Tobe O homepage: https://github.com/angel-dart/jael/tree/master/jael