+3
This commit is contained in:
parent
10cb608f1e
commit
14db5f99c5
9 changed files with 95 additions and 12 deletions
|
@ -3,6 +3,7 @@ export 'ast_node.dart';
|
||||||
export 'attribute.dart';
|
export 'attribute.dart';
|
||||||
export 'binary.dart';
|
export 'binary.dart';
|
||||||
export 'call.dart';
|
export 'call.dart';
|
||||||
|
export 'conditional.dart';
|
||||||
export 'document.dart';
|
export 'document.dart';
|
||||||
export 'element.dart';
|
export 'element.dart';
|
||||||
export 'error.dart';
|
export 'error.dart';
|
||||||
|
|
27
jael/lib/src/ast/conditional.dart
Normal file
27
jael/lib/src/ast/conditional.dart
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,6 +15,15 @@ class TextNode extends ElementChild {
|
||||||
FileSpan get span => text.span;
|
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 {
|
abstract class Element extends ElementChild {
|
||||||
static const List<String> selfClosing = const [
|
static const List<String> selfClosing = const [
|
||||||
'include',
|
'include',
|
||||||
|
|
|
@ -23,6 +23,7 @@ enum TokenType {
|
||||||
slash,
|
slash,
|
||||||
equals,
|
equals,
|
||||||
id,
|
id,
|
||||||
|
script_tag,
|
||||||
text,
|
text,
|
||||||
|
|
||||||
// Keywords
|
// Keywords
|
||||||
|
|
|
@ -166,6 +166,8 @@ class Renderer {
|
||||||
buffer.write(child.span.text.trimRight());
|
buffer.write(child.span.text.trimRight());
|
||||||
else
|
else
|
||||||
buffer.write(child.span.text);
|
buffer.write(child.span.text);
|
||||||
|
} else if (child is ScriptTag) {
|
||||||
|
buffer.writeln(child.script_tag.span.text);
|
||||||
} else if (child is Interpolation) {
|
} else if (child is Interpolation) {
|
||||||
var value = child.expression.compute(scope);
|
var value = child.expression.compute(scope);
|
||||||
|
|
||||||
|
@ -176,7 +178,7 @@ class Renderer {
|
||||||
buffer.write(HTML_ESCAPE.convert(value.toString()));
|
buffer.write(HTML_ESCAPE.convert(value.toString()));
|
||||||
}
|
}
|
||||||
} else if (child is Element) {
|
} else if (child is Element) {
|
||||||
buffer.writeln();
|
if (buffer?.lastLine?.text?.isNotEmpty == true) buffer.writeln();
|
||||||
renderElement(child, buffer, scope, html5);
|
renderElement(child, buffer, scope, html5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,9 +16,45 @@ const Map<TokenType, InfixParselet> infixParselets = const {
|
||||||
TokenType.gte: const BinaryParselet(11),
|
TokenType.gte: const BinaryParselet(11),
|
||||||
TokenType.equ: const BinaryParselet(10),
|
TokenType.equ: const BinaryParselet(10),
|
||||||
TokenType.nequ: const BinaryParselet(10),
|
TokenType.nequ: const BinaryParselet(10),
|
||||||
|
TokenType.question: const ConditionalParselet(),
|
||||||
TokenType.equals: const BinaryParselet(3),
|
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 {
|
class BinaryParselet implements InfixParselet {
|
||||||
final int precedence;
|
final int precedence;
|
||||||
|
|
||||||
|
|
|
@ -68,9 +68,7 @@ class Parser {
|
||||||
StringLiteral implicitString() {
|
StringLiteral implicitString() {
|
||||||
if (next(TokenType.string)) {
|
if (next(TokenType.string)) {
|
||||||
return prefixParselets[TokenType.string].parse(this, _current);
|
return prefixParselets[TokenType.string].parse(this, _current);
|
||||||
} else if (next(TokenType.text)) {
|
} else if (next(TokenType.text)) {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -85,8 +83,10 @@ class Parser {
|
||||||
}
|
}
|
||||||
var doctype = _current, html = parseIdentifier();
|
var doctype = _current, html = parseIdentifier();
|
||||||
if (html?.span?.text?.toLowerCase() != 'html') {
|
if (html?.span?.text?.toLowerCase() != 'html') {
|
||||||
errors.add(new JaelError(JaelErrorSeverity.error,
|
errors.add(new JaelError(
|
||||||
'Expected "html" in doctype declaration.', html?.span ?? doctype.span));
|
JaelErrorSeverity.error,
|
||||||
|
'Expected "html" in doctype declaration.',
|
||||||
|
html?.span ?? doctype.span));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,8 +102,10 @@ class Parser {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (public?.span?.text?.toLowerCase() != 'public') {
|
if (public?.span?.text?.toLowerCase() != 'public') {
|
||||||
errors.add(new JaelError(JaelErrorSeverity.error,
|
errors.add(new JaelError(
|
||||||
'Expected "public" in doctype declaration.', public?.span ?? html.span));
|
JaelErrorSeverity.error,
|
||||||
|
'Expected "public" in doctype declaration.',
|
||||||
|
public?.span ?? html.span));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,6 +140,7 @@ class Parser {
|
||||||
parseHtmlComment() ??
|
parseHtmlComment() ??
|
||||||
parseInterpolation() ??
|
parseInterpolation() ??
|
||||||
parseText() ??
|
parseText() ??
|
||||||
|
parseScriptTag() ??
|
||||||
parseElement();
|
parseElement();
|
||||||
|
|
||||||
HtmlComment parseHtmlComment() =>
|
HtmlComment parseHtmlComment() =>
|
||||||
|
@ -145,6 +148,9 @@ class Parser {
|
||||||
|
|
||||||
Text parseText() => next(TokenType.text) ? new Text(_current) : null;
|
Text parseText() => next(TokenType.text) ? new Text(_current) : null;
|
||||||
|
|
||||||
|
ScriptTag parseScriptTag() =>
|
||||||
|
next(TokenType.script_tag) ? new ScriptTag(_current) : null;
|
||||||
|
|
||||||
Interpolation parseInterpolation() {
|
Interpolation parseInterpolation() {
|
||||||
if (!next(TokenType.doubleCurlyL)) return null;
|
if (!next(TokenType.doubleCurlyL)) return null;
|
||||||
var doubleCurlyL = _current;
|
var doubleCurlyL = _current;
|
||||||
|
@ -301,7 +307,8 @@ class Parser {
|
||||||
while (precedence < _nextPrecedence()) {
|
while (precedence < _nextPrecedence()) {
|
||||||
_current = scanner.tokens[++_index];
|
_current = scanner.tokens[++_index];
|
||||||
|
|
||||||
if (_current.type == TokenType.slash && peek()?.type == TokenType.gt) {
|
if (_current.type == TokenType.slash &&
|
||||||
|
peek()?.type == TokenType.gt) {
|
||||||
// Handle `/>`
|
// Handle `/>`
|
||||||
//
|
//
|
||||||
// Don't register this as an infix expression.
|
// Don't register this as an infix expression.
|
||||||
|
@ -314,8 +321,7 @@ class Parser {
|
||||||
var newLeft = infix.parse(this, left, _current);
|
var newLeft = infix.parse(this, left, _current);
|
||||||
|
|
||||||
if (newLeft == null) {
|
if (newLeft == null) {
|
||||||
if (_current.type == TokenType.gt)
|
if (_current.type == TokenType.gt) _index--;
|
||||||
_index--;
|
|
||||||
return left;
|
return left;
|
||||||
}
|
}
|
||||||
left = newLeft;
|
left = newLeft;
|
||||||
|
|
|
@ -30,6 +30,7 @@ final Map<Pattern, TokenType> _htmlPatterns = {
|
||||||
'=': TokenType.equals,
|
'=': TokenType.equals,
|
||||||
_string1: TokenType.string,
|
_string1: TokenType.string,
|
||||||
_string2: TokenType.string,
|
_string2: TokenType.string,
|
||||||
|
new RegExp(r'<script[^>]*>[^$]*</script>'): TokenType.script_tag,
|
||||||
new RegExp(r'([A-Za-z][A-Za-z0-9]*-)*([A-Za-z][A-Za-z0-9]*)'): TokenType.id,
|
new RegExp(r'([A-Za-z][A-Za-z0-9]*-)*([A-Za-z][A-Za-z0-9]*)'): TokenType.id,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
name: jael
|
name: jael
|
||||||
version: 1.0.0-alpha+2
|
version: 1.0.0-alpha+3
|
||||||
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
|
||||||
|
|
Loading…
Reference in a new issue