This commit is contained in:
Tobe O 2017-10-01 00:44:57 -04:00
parent 10cb608f1e
commit 14db5f99c5
9 changed files with 95 additions and 12 deletions

View file

@ -3,6 +3,7 @@ export 'ast_node.dart';
export 'attribute.dart';
export 'binary.dart';
export 'call.dart';
export 'conditional.dart';
export 'document.dart';
export 'element.dart';
export 'error.dart';

View 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);
}
}

View file

@ -15,6 +15,15 @@ class TextNode extends ElementChild {
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 {
static const List<String> selfClosing = const [
'include',

View file

@ -23,6 +23,7 @@ enum TokenType {
slash,
equals,
id,
script_tag,
text,
// Keywords

View file

@ -166,6 +166,8 @@ class Renderer {
buffer.write(child.span.text.trimRight());
else
buffer.write(child.span.text);
} else if (child is ScriptTag) {
buffer.writeln(child.script_tag.span.text);
} else if (child is Interpolation) {
var value = child.expression.compute(scope);
@ -176,7 +178,7 @@ class Renderer {
buffer.write(HTML_ESCAPE.convert(value.toString()));
}
} else if (child is Element) {
buffer.writeln();
if (buffer?.lastLine?.text?.isNotEmpty == true) buffer.writeln();
renderElement(child, buffer, scope, html5);
}
}

View file

@ -16,9 +16,45 @@ const Map<TokenType, InfixParselet> infixParselets = const {
TokenType.gte: const BinaryParselet(11),
TokenType.equ: const BinaryParselet(10),
TokenType.nequ: const BinaryParselet(10),
TokenType.question: const ConditionalParselet(),
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 {
final int precedence;

View file

@ -68,9 +68,7 @@ class Parser {
StringLiteral implicitString() {
if (next(TokenType.string)) {
return prefixParselets[TokenType.string].parse(this, _current);
} else if (next(TokenType.text)) {
}
} else if (next(TokenType.text)) {}
return null;
}
@ -85,8 +83,10 @@ class Parser {
}
var doctype = _current, html = parseIdentifier();
if (html?.span?.text?.toLowerCase() != 'html') {
errors.add(new JaelError(JaelErrorSeverity.error,
'Expected "html" in doctype declaration.', html?.span ?? doctype.span));
errors.add(new JaelError(
JaelErrorSeverity.error,
'Expected "html" in doctype declaration.',
html?.span ?? doctype.span));
return null;
}
@ -102,8 +102,10 @@ class Parser {
}
if (public?.span?.text?.toLowerCase() != 'public') {
errors.add(new JaelError(JaelErrorSeverity.error,
'Expected "public" in doctype declaration.', public?.span ?? html.span));
errors.add(new JaelError(
JaelErrorSeverity.error,
'Expected "public" in doctype declaration.',
public?.span ?? html.span));
return null;
}
@ -138,6 +140,7 @@ class Parser {
parseHtmlComment() ??
parseInterpolation() ??
parseText() ??
parseScriptTag() ??
parseElement();
HtmlComment parseHtmlComment() =>
@ -145,6 +148,9 @@ class Parser {
Text parseText() => next(TokenType.text) ? new Text(_current) : null;
ScriptTag parseScriptTag() =>
next(TokenType.script_tag) ? new ScriptTag(_current) : null;
Interpolation parseInterpolation() {
if (!next(TokenType.doubleCurlyL)) return null;
var doubleCurlyL = _current;
@ -301,7 +307,8 @@ class Parser {
while (precedence < _nextPrecedence()) {
_current = scanner.tokens[++_index];
if (_current.type == TokenType.slash && peek()?.type == TokenType.gt) {
if (_current.type == TokenType.slash &&
peek()?.type == TokenType.gt) {
// Handle `/>`
//
// Don't register this as an infix expression.
@ -314,8 +321,7 @@ class Parser {
var newLeft = infix.parse(this, left, _current);
if (newLeft == null) {
if (_current.type == TokenType.gt)
_index--;
if (_current.type == TokenType.gt) _index--;
return left;
}
left = newLeft;

View file

@ -30,6 +30,7 @@ final Map<Pattern, TokenType> _htmlPatterns = {
'=': TokenType.equals,
_string1: 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,
};

View file

@ -1,5 +1,5 @@
name: jael
version: 1.0.0-alpha+2
version: 1.0.0-alpha+3
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