null-coalescing
This commit is contained in:
parent
f16f2918a3
commit
7c07ad3aa3
10 changed files with 73 additions and 14 deletions
|
@ -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;
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.');
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,7 @@ enum TokenType {
|
||||||
colon,
|
colon,
|
||||||
comma,
|
comma,
|
||||||
dot,
|
dot,
|
||||||
|
exclamation,
|
||||||
percent,
|
percent,
|
||||||
plus,
|
plus,
|
||||||
minus,
|
minus,
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
'''
|
'''
|
||||||
|
|
Loading…
Reference in a new issue