platform/jael/lib/src/text/parser.dart
2017-09-29 18:39:37 -04:00

362 lines
9.3 KiB
Dart

import '../ast/ast.dart';
import 'parselet/parselet.dart';
import 'scanner.dart';
class Parser {
final List<JaelError> errors = [];
final Scanner scanner;
Token _current;
int _index = -1;
Parser(this.scanner);
Token get current => _current;
int _nextPrecedence() {
var tok = peek();
if (tok == null) return 0;
var parser = infixParselets[tok.type];
return parser?.precedence ?? 0;
}
bool next(TokenType type) {
if (_index >= scanner.tokens.length - 1) return false;
var peek = scanner.tokens[_index + 1];
if (peek.type != type) return false;
_current = peek;
_index++;
return true;
}
Token peek() {
if (_index >= scanner.tokens.length - 1) return null;
return scanner.tokens[_index + 1];
}
Token maybe(TokenType type) => next(type) ? _current : null;
void skipExtraneous(TokenType type) {
while (next(type)) {
// Skip...
}
}
Document parseDocument() {
var doctype = parseDoctype();
if (doctype == null) {
var root = parseElement();
if (root == null) return null;
return new Document(null, root);
}
var root = parseElement();
if (root == null) {
errors.add(new JaelError(JaelErrorSeverity.error,
'Missing root element after !DOCTYPE declaration.', doctype.span));
return null;
}
return new Document(doctype, root);
}
StringLiteral implicitString() {
if (next(TokenType.string)) {
return prefixParselets[TokenType.string].parse(this, _current);
} else if (next(TokenType.text)) {
}
return null;
}
Doctype parseDoctype() {
if (!next(TokenType.lt)) return null;
var lt = _current;
if (!next(TokenType.doctype)) {
_index--;
return null;
}
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));
return null;
}
var public = parseIdentifier();
if (public == null) {
if (!next(TokenType.gt)) {
errors.add(new JaelError(JaelErrorSeverity.error,
'Expected ">" in doctype declaration.', html.span));
return null;
}
return new Doctype(lt, doctype, html, null, null, null, _current);
}
if (public?.span?.text?.toLowerCase() != 'public') {
errors.add(new JaelError(JaelErrorSeverity.error,
'Expected "public" in doctype declaration.', public?.span ?? html.span));
return null;
}
var stringParser = prefixParselets[TokenType.string];
if (!next(TokenType.string)) {
errors.add(new JaelError(JaelErrorSeverity.error,
'Expected string in doctype declaration.', public.span));
return null;
}
var name = stringParser.parse(this, _current);
if (!next(TokenType.string)) {
errors.add(new JaelError(JaelErrorSeverity.error,
'Expected string in doctype declaration.', name.span));
return null;
}
var url = stringParser.parse(this, _current);
if (!next(TokenType.gt)) {
errors.add(new JaelError(JaelErrorSeverity.error,
'Expected ">" in doctype declaration.', url.span));
return null;
}
return new Doctype(lt, doctype, html, public, name, url, _current);
}
ElementChild parseElementChild() =>
parseHtmlComment() ??
parseInterpolation() ??
parseText() ??
parseElement();
HtmlComment parseHtmlComment() =>
next(TokenType.htmlComment) ? new HtmlComment(_current) : null;
Text parseText() => next(TokenType.text) ? new Text(_current) : null;
Interpolation parseInterpolation() {
if (!next(TokenType.doubleCurlyL)) return null;
var doubleCurlyL = _current;
var expression = parseExpression(0);
if (expression == null) {
errors.add(new JaelError(JaelErrorSeverity.error,
'Missing expression in interpolation.', doubleCurlyL.span));
return null;
}
if (!next(TokenType.doubleCurlyR)) {
errors.add(new JaelError(JaelErrorSeverity.error,
'Missing closing "}}" in interpolation.', expression.span));
return null;
}
return new Interpolation(doubleCurlyL, expression, _current);
}
Element parseElement() {
if (!next(TokenType.lt)) return null;
var lt = _current;
if (next(TokenType.slash)) {
// We entered a closing tag, don't keep reading...
_index -= 2;
return null;
}
var tagName = parseIdentifier();
if (tagName == null) {
errors.add(
new JaelError(JaelErrorSeverity.error, 'Missing tag name.', lt.span));
return null;
}
List<Attribute> attributes = [];
var attribute = parseAttribute();
while (attribute != null) {
attributes.add(attribute);
attribute = parseAttribute();
}
if (next(TokenType.slash)) {
// Try for self-closing...
var slash = _current;
if (!next(TokenType.gt)) {
errors.add(new JaelError(JaelErrorSeverity.error,
'Missing ">" in self-closing "${tagName.name}" tag.', slash.span));
return null;
}
return new SelfClosingElement(lt, tagName, attributes, slash, _current);
}
if (!next(TokenType.gt)) {
errors.add(new JaelError(
JaelErrorSeverity.error,
'Missing ">" in "${tagName.name}" tag.',
attributes.isEmpty ? tagName.span : attributes.last.span));
return null;
}
var gt = _current;
// Implicit self-closing
if (Element.selfClosing.contains(tagName.name)) {
return new SelfClosingElement(lt, tagName, attributes, null, gt);
}
List<ElementChild> children = [];
var child = parseElementChild();
while (child != null) {
if (child is! HtmlComment) children.add(child);
child = parseElementChild();
}
// Parse closing tag
if (!next(TokenType.lt)) {
errors.add(new JaelError(
JaelErrorSeverity.error,
'Missing closing tag for "${tagName.name}" tag.',
children.isEmpty ? tagName.span : children.last.span));
return null;
}
var lt2 = _current;
if (!next(TokenType.slash)) {
errors.add(new JaelError(JaelErrorSeverity.error,
'Missing "/" in "${tagName.name}" closing tag.', lt2.span));
return null;
}
var slash = _current, tagName2 = parseIdentifier();
if (tagName2 == null) {
errors.add(new JaelError(
JaelErrorSeverity.error,
'Missing "${tagName.name}" in "${tagName.name}" closing tag.',
slash.span));
return null;
}
if (tagName2.name != tagName.name) {
errors.add(new JaelError(
JaelErrorSeverity.error,
'Mismatched closing tags. Expected "${tagName.span}"; got "${tagName2.name}" instead.',
lt2.span));
return null;
}
if (!next(TokenType.gt)) {
errors.add(new JaelError(JaelErrorSeverity.error,
'Missing ">" in "${tagName.name}" closing tag.', tagName2.span));
return null;
}
return new RegularElement(
lt, tagName, attributes, gt, children, lt2, slash, tagName2, _current);
}
Attribute parseAttribute() {
var name = parseIdentifier();
if (name == null) return null;
if (!next(TokenType.equals)) return new Attribute(name, null, null);
var equals = _current;
var value = parseExpression(0);
if (value == null) {
errors.add(new JaelError(JaelErrorSeverity.error,
'Missing expression in attribute.', equals.span));
return null;
}
return new Attribute(name, equals, value);
}
Expression parseExpression(int precedence) {
// Only consume a token if it could potentially be a prefix parselet
for (var type in prefixParselets.keys) {
if (next(type)) {
var left = prefixParselets[type].parse(this, _current);
while (precedence < _nextPrecedence()) {
_current = scanner.tokens[++_index];
var infix = infixParselets[_current.type];
var newLeft = infix.parse(this, left, _current);
if (newLeft == null) {
if (_current.type == TokenType.gt)
_index--;
return left;
}
left = newLeft;
}
return left;
}
}
// Nothing was parsed; return null.
return null;
}
Identifier parseIdentifier() =>
next(TokenType.id) ? new Identifier(_current) : null;
KeyValuePair parseKeyValuePair() {
var key = parseExpression(0);
if (key == null) return null;
if (!next(TokenType.colon)) return new KeyValuePair(key, null, null);
var colon = _current, value = parseExpression(0);
if (value == null) {
errors.add(new JaelError(JaelErrorSeverity.error,
'Missing expression in key-value pair.', colon.span));
return null;
}
return new KeyValuePair(key, colon, value);
}
NamedArgument parseNamedArgument() {
var name = parseIdentifier();
if (name == null) return null;
if (!next(TokenType.colon)) {
errors.add(new JaelError(JaelErrorSeverity.error,
'Missing ":" in named argument.', name.span));
return null;
}
var colon = _current, value = parseExpression(0);
if (value == null) {
errors.add(new JaelError(JaelErrorSeverity.error,
'Missing expression in named argument.', colon.span));
return null;
}
return new NamedArgument(name, colon, value);
}
}