null-coalescing

This commit is contained in:
Tobe O 2017-10-16 18:48:35 -04:00
parent f16f2918a3
commit 7c07ad3aa3
10 changed files with 73 additions and 14 deletions

View file

@ -1,6 +1,6 @@
import 'package:source_span/source_span.dart'; import 'package:source_span/source_span.dart';
class JaelError { class JaelError extends Error {
final JaelErrorSeverity severity; final JaelErrorSeverity severity;
final String message; final String message;
final FileSpan span; final FileSpan span;

View file

@ -1,8 +1,27 @@
import 'package:source_span/source_span.dart';
import 'package:symbol_table/symbol_table.dart'; import 'package:symbol_table/symbol_table.dart';
import 'ast_node.dart'; import 'ast_node.dart';
import 'token.dart';
abstract class Expression extends AstNode { abstract class Expression extends AstNode {
compute(SymbolTable scope); compute(SymbolTable scope);
} }
abstract class Literal extends Expression {} 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));
}
}

View file

@ -20,6 +20,8 @@ class Identifier extends Expression {
default: default:
var symbol = scope.resolve(name); var symbol = scope.resolve(name);
if (symbol == null) { if (symbol == null) {
if (scope.resolve('!strict!')?.value == false)
return null;
throw new ArgumentError( throw new ArgumentError(
'The name "$name" does not exist in this scope.'); 'The name "$name" does not exist in this scope.');
} }

View file

@ -43,6 +43,7 @@ enum TokenType {
colon, colon,
comma, comma,
dot, dot,
exclamation,
percent, percent,
plus, plus,
minus, minus,

View file

@ -27,7 +27,13 @@ Document parseDocument(String text,
class Renderer { class Renderer {
const 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); if (document.doctype != null) buffer.writeln(document.doctype.span.text);
renderElement( renderElement(
document.root, buffer, scope, document.doctype?.public == null); document.root, buffer, scope, document.doctype?.public == null);

View file

@ -18,6 +18,7 @@ const Map<TokenType, InfixParselet> infixParselets = const {
TokenType.nequ: const BinaryParselet(10), TokenType.nequ: const BinaryParselet(10),
TokenType.question: const ConditionalParselet(), TokenType.question: const ConditionalParselet(),
TokenType.equals: const BinaryParselet(3), TokenType.equals: const BinaryParselet(3),
TokenType.elvis: const BinaryParselet(3),
}; };
class ConditionalParselet implements InfixParselet { class ConditionalParselet implements InfixParselet {

View file

@ -1,6 +1,7 @@
part of jael.src.text.parselet; part of jael.src.text.parselet;
const Map<TokenType, PrefixParselet> prefixParselets = const { const Map<TokenType, PrefixParselet> prefixParselets = const {
TokenType.exclamation: const NotParselet(),
TokenType.$new: const NewParselet(), TokenType.$new: const NewParselet(),
TokenType.number: const NumberParselet(), TokenType.number: const NumberParselet(),
TokenType.hex: const HexParselet(), TokenType.hex: const HexParselet(),
@ -11,6 +12,22 @@ const Map<TokenType, PrefixParselet> prefixParselets = const {
TokenType.lParen: const ParenthesisParselet(), 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 { class NewParselet implements PrefixParselet {
const NewParselet(); const NewParselet();

View file

@ -3,6 +3,7 @@ import '../ast/ast.dart';
final RegExp _whitespace = new RegExp(r'[ \n\r\t]+'); 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( final RegExp _string1 = new RegExp(
r"'((\\(['\\/bfnrt]|(u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])))|([^'\\]))*'"); r"'((\\(['\\/bfnrt]|(u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])))|([^'\\]))*'");
final RegExp _string2 = new RegExp( final RegExp _string2 = new RegExp(
@ -31,7 +32,7 @@ final Map<Pattern, TokenType> _htmlPatterns = {
'!=': TokenType.nequ, '!=': TokenType.nequ,
_string1: TokenType.string, _string1: TokenType.string,
_string2: 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<Pattern, TokenType> _expressionPatterns = { final Map<Pattern, TokenType> _expressionPatterns = {
@ -45,7 +46,10 @@ final Map<Pattern, TokenType> _expressionPatterns = {
':': TokenType.colon, ':': TokenType.colon,
',': TokenType.comma, ',': TokenType.comma,
'.': TokenType.dot, '.': TokenType.dot,
'??': TokenType.elvis,
'?.': TokenType.elvis_dot,
'=': TokenType.equals, '=': TokenType.equals,
'!': TokenType.exclamation,
'-': TokenType.minus, '-': TokenType.minus,
'%': TokenType.percent, '%': TokenType.percent,
'+': TokenType.plus, '+': TokenType.plus,
@ -67,7 +71,7 @@ final Map<Pattern, TokenType> _expressionPatterns = {
new RegExp(r'0x[A-Fa-f0-9]+'): TokenType.hex, new RegExp(r'0x[A-Fa-f0-9]+'): TokenType.hex,
_string1: TokenType.string, _string1: TokenType.string,
_string2: TokenType.string, _string2: TokenType.string,
new RegExp('[A-Za-z_\\\$][A-Za-z0-9_\\\$]*'): TokenType.id, _id: TokenType.id,
}; };
class _Scanner implements Scanner { class _Scanner implements Scanner {

View file

@ -1,5 +1,5 @@
name: jael name: jael
version: 1.0.0-beta+1 version: 1.0.0-beta+2
description: A simple server-side HTML templating engine for Dart. description: A simple server-side HTML templating engine for Dart.
author: Tobe O <thosakwe@gmail.com> author: Tobe O <thosakwe@gmail.com>
homepage: https://github.com/angel-dart/jael/tree/master/jael homepage: https://github.com/angel-dart/jael/tree/master/jael

View file

@ -9,19 +9,28 @@ main() {
<html> <html>
<body> <body>
<h1>Hello</h1> <h1>Hello</h1>
<img src=profile['avatar'] /> <img ready="always" data-img-src=profile['avatar'] />
</body> </body>
</html> </html>
'''; ''';
var buf = new CodeBuffer(); var buf = new CodeBuffer();
var document = jael.parseDocument(template, sourceUrl: 'test.jl'); jael.Document document;
var scope = new SymbolTable(values: { SymbolTable scope;
try {
document = jael.parseDocument(template, sourceUrl: 'test.jl');
scope = new SymbolTable(values: {
'profile': { 'profile': {
'avatar': 'thosakwe.png', 'avatar': 'thosakwe.png',
} }
}); });
} on jael.JaelError catch(e) {
print(e);
print(e.stackTrace);
}
expect(document, isNotNull);
const jael.Renderer().render(document, buf, scope); const jael.Renderer().render(document, buf, scope);
print(buf); print(buf);
@ -33,7 +42,7 @@ main() {
<h1> <h1>
Hello Hello
</h1> </h1>
<img src="thosakwe.png"> <img ready="always" data-img-src="thosakwe.png">
</body> </body>
</html> </html>
''' '''