From 7c07ad3aa31976c205460759b329dab748333518 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Mon, 16 Oct 2017 18:48:35 -0400 Subject: [PATCH] null-coalescing --- jael/lib/src/ast/error.dart | 2 +- jael/lib/src/ast/expression.dart | 21 ++++++++++++++++++++- jael/lib/src/ast/identifier.dart | 2 ++ jael/lib/src/ast/token.dart | 1 + jael/lib/src/renderer.dart | 8 +++++++- jael/lib/src/text/parselet/infix.dart | 1 + jael/lib/src/text/parselet/prefix.dart | 17 +++++++++++++++++ jael/lib/src/text/scanner.dart | 8 ++++++-- jael/pubspec.yaml | 2 +- jael/test/render/render_test.dart | 25 +++++++++++++++++-------- 10 files changed, 73 insertions(+), 14 deletions(-) diff --git a/jael/lib/src/ast/error.dart b/jael/lib/src/ast/error.dart index 350ba67d..f931a126 100644 --- a/jael/lib/src/ast/error.dart +++ b/jael/lib/src/ast/error.dart @@ -1,6 +1,6 @@ import 'package:source_span/source_span.dart'; -class JaelError { +class JaelError extends Error { final JaelErrorSeverity severity; final String message; final FileSpan span; diff --git a/jael/lib/src/ast/expression.dart b/jael/lib/src/ast/expression.dart index 5ad70635..589939cb 100644 --- a/jael/lib/src/ast/expression.dart +++ b/jael/lib/src/ast/expression.dart @@ -1,8 +1,27 @@ +import 'package:source_span/source_span.dart'; import 'package:symbol_table/symbol_table.dart'; import 'ast_node.dart'; +import 'token.dart'; abstract class Expression extends AstNode { compute(SymbolTable scope); } -abstract class Literal extends Expression {} \ No newline at end of file +abstract class Literal extends Expression {} + +class Negation extends Expression { + final Token exclamation; + final Expression expression; + + Negation(this.exclamation, this.expression); + + @override + FileSpan get span { + return exclamation.span.expand(expression.span); + } + + @override + compute(SymbolTable scope) { + return !(expression.compute(scope)); + } +} \ No newline at end of file diff --git a/jael/lib/src/ast/identifier.dart b/jael/lib/src/ast/identifier.dart index aa9a0f2b..903898d5 100644 --- a/jael/lib/src/ast/identifier.dart +++ b/jael/lib/src/ast/identifier.dart @@ -20,6 +20,8 @@ class Identifier extends Expression { default: var symbol = scope.resolve(name); if (symbol == null) { + if (scope.resolve('!strict!')?.value == false) + return null; throw new ArgumentError( 'The name "$name" does not exist in this scope.'); } diff --git a/jael/lib/src/ast/token.dart b/jael/lib/src/ast/token.dart index 2d3ee0a8..af629328 100644 --- a/jael/lib/src/ast/token.dart +++ b/jael/lib/src/ast/token.dart @@ -43,6 +43,7 @@ enum TokenType { colon, comma, dot, + exclamation, percent, plus, minus, diff --git a/jael/lib/src/renderer.dart b/jael/lib/src/renderer.dart index 2970540f..011535ec 100644 --- a/jael/lib/src/renderer.dart +++ b/jael/lib/src/renderer.dart @@ -27,7 +27,13 @@ Document parseDocument(String text, class Renderer { const Renderer(); - void render(Document document, CodeBuffer buffer, SymbolTable scope) { + /// Renders a [document] into the [buffer] as HTML. + /// + /// If [strictResolution] is `false` (default: `true`), then undefined identifiers will return `null` + /// instead of throwing. + void render(Document document, CodeBuffer buffer, SymbolTable scope, {bool strictResolution: true}) { + scope.add('!strict!', value: strictResolution != false); + if (document.doctype != null) buffer.writeln(document.doctype.span.text); renderElement( document.root, buffer, scope, document.doctype?.public == null); diff --git a/jael/lib/src/text/parselet/infix.dart b/jael/lib/src/text/parselet/infix.dart index c558845e..56f18842 100644 --- a/jael/lib/src/text/parselet/infix.dart +++ b/jael/lib/src/text/parselet/infix.dart @@ -18,6 +18,7 @@ const Map infixParselets = const { TokenType.nequ: const BinaryParselet(10), TokenType.question: const ConditionalParselet(), TokenType.equals: const BinaryParselet(3), + TokenType.elvis: const BinaryParselet(3), }; class ConditionalParselet implements InfixParselet { diff --git a/jael/lib/src/text/parselet/prefix.dart b/jael/lib/src/text/parselet/prefix.dart index 3c17d2e9..4b3f3a50 100644 --- a/jael/lib/src/text/parselet/prefix.dart +++ b/jael/lib/src/text/parselet/prefix.dart @@ -1,6 +1,7 @@ part of jael.src.text.parselet; const Map prefixParselets = const { + TokenType.exclamation: const NotParselet(), TokenType.$new: const NewParselet(), TokenType.number: const NumberParselet(), TokenType.hex: const HexParselet(), @@ -11,6 +12,22 @@ const Map prefixParselets = const { TokenType.lParen: const ParenthesisParselet(), }; +class NotParselet implements PrefixParselet { + const NotParselet(); + + @override + Expression parse(Parser parser, Token token) { + var expression = parser.parseExpression(0); + + if (expression == null) { + parser.errors.add(new JaelError(JaelErrorSeverity.error, + 'Missing expression after "!" in negation expression.', token.span)); + } + + return new Negation(token, expression); + } +} + class NewParselet implements PrefixParselet { const NewParselet(); diff --git a/jael/lib/src/text/scanner.dart b/jael/lib/src/text/scanner.dart index 7a4dc0e6..11e795e8 100644 --- a/jael/lib/src/text/scanner.dart +++ b/jael/lib/src/text/scanner.dart @@ -3,6 +3,7 @@ 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 _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( @@ -31,7 +32,7 @@ final Map _htmlPatterns = { '!=': TokenType.nequ, _string1: TokenType.string, _string2: TokenType.string, - new RegExp(r'(([A-Za-z][A-Za-z0-9]*-)*([A-Za-z][A-Za-z0-9]*))'): TokenType.id, + _id: TokenType.id, }; final Map _expressionPatterns = { @@ -45,7 +46,10 @@ final Map _expressionPatterns = { ':': TokenType.colon, ',': TokenType.comma, '.': TokenType.dot, + '??': TokenType.elvis, + '?.': TokenType.elvis_dot, '=': TokenType.equals, + '!': TokenType.exclamation, '-': TokenType.minus, '%': TokenType.percent, '+': TokenType.plus, @@ -67,7 +71,7 @@ final Map _expressionPatterns = { new RegExp(r'0x[A-Fa-f0-9]+'): TokenType.hex, _string1: TokenType.string, _string2: TokenType.string, - new RegExp('[A-Za-z_\\\$][A-Za-z0-9_\\\$]*'): TokenType.id, + _id: TokenType.id, }; class _Scanner implements Scanner { diff --git a/jael/pubspec.yaml b/jael/pubspec.yaml index c0545dd6..eacba15a 100644 --- a/jael/pubspec.yaml +++ b/jael/pubspec.yaml @@ -1,5 +1,5 @@ name: jael -version: 1.0.0-beta+1 +version: 1.0.0-beta+2 description: A simple server-side HTML templating engine for Dart. author: Tobe O homepage: https://github.com/angel-dart/jael/tree/master/jael diff --git a/jael/test/render/render_test.dart b/jael/test/render/render_test.dart index 83e6dd5c..5ff21424 100644 --- a/jael/test/render/render_test.dart +++ b/jael/test/render/render_test.dart @@ -9,19 +9,28 @@ main() {

Hello

- + '''; var buf = new CodeBuffer(); - var document = jael.parseDocument(template, sourceUrl: 'test.jl'); - var scope = new SymbolTable(values: { - 'profile': { - 'avatar': 'thosakwe.png', - } - }); + jael.Document document; + SymbolTable scope; + try { + document = jael.parseDocument(template, sourceUrl: 'test.jl'); + scope = new SymbolTable(values: { + 'profile': { + 'avatar': 'thosakwe.png', + } + }); + } on jael.JaelError catch(e) { + print(e); + print(e.stackTrace); + } + + expect(document, isNotNull); const jael.Renderer().render(document, buf, scope); print(buf); @@ -33,7 +42,7 @@ main() {

Hello

- + '''