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';
class JaelError {
class JaelError extends Error {
final JaelErrorSeverity severity;
final String message;
final FileSpan span;

View file

@ -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 {}
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:
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.');
}

View file

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

View file

@ -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);

View file

@ -18,6 +18,7 @@ const Map<TokenType, InfixParselet> 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 {

View file

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

View file

@ -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<Pattern, TokenType> _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<Pattern, TokenType> _expressionPatterns = {
@ -45,7 +46,10 @@ final Map<Pattern, TokenType> _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<Pattern, TokenType> _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 {

View file

@ -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 <thosakwe@gmail.com>
homepage: https://github.com/angel-dart/jael/tree/master/jael

View file

@ -9,19 +9,28 @@ main() {
<html>
<body>
<h1>Hello</h1>
<img src=profile['avatar'] />
<img ready="always" data-img-src=profile['avatar'] />
</body>
</html>
''';
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() {
<h1>
Hello
</h1>
<img src="thosakwe.png">
<img ready="always" data-img-src="thosakwe.png">
</body>
</html>
'''