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 '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';

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; 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',

View file

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

View file

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

View file

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

View file

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

View file

@ -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,
}; };

View file

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