blob: 91a26630246870f4053f5405c1e58f819a1e978f [file] [log] [blame]
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:source_span/source_span.dart';
import 'ast.dart';
import 'scanner.dart';
import 'token.dart';
/// A class for parsing a boolean selector.
///
/// Boolean selectors use a stripped-down version of the Dart expression syntax
/// that only contains variables, parentheses, and boolean operators. Variables
/// may also contain dashes, contrary to Dart's syntax; this allows consistency
/// with command-line arguments.
class Parser {
/// The scanner that tokenizes the selector.
final Scanner _scanner;
Parser(String selector) : _scanner = Scanner(selector);
/// Parses the selector.
///
/// This must only be called once per parser.
Node parse() {
var selector = _conditional();
if (_scanner.peek().type != TokenType.endOfFile) {
throw SourceSpanFormatException(
"Expected end of input.", _scanner.peek().span);
}
return selector;
}
/// Parses a conditional:
///
/// conditionalExpression:
/// logicalOrExpression ("?" conditionalExpression ":"
/// conditionalExpression)?
Node _conditional() {
var condition = _or();
if (!_scanner.scan(TokenType.questionMark)) return condition;
var whenTrue = _conditional();
if (!_scanner.scan(TokenType.colon)) {
throw SourceSpanFormatException('Expected ":".', _scanner.peek().span);
}
var whenFalse = _conditional();
return ConditionalNode(condition, whenTrue, whenFalse);
}
/// Parses a logical or:
///
/// logicalOrExpression:
/// logicalAndExpression ("||" logicalOrExpression)?
Node _or() {
var left = _and();
if (!_scanner.scan(TokenType.or)) return left;
return OrNode(left, _or());
}
/// Parses a logical and:
///
/// logicalAndExpression:
/// simpleExpression ("&&" logicalAndExpression)?
Node _and() {
var left = _simpleExpression();
if (!_scanner.scan(TokenType.and)) return left;
return AndNode(left, _and());
}
/// Parses a simple expression:
///
/// simpleExpression:
/// "!" simpleExpression |
/// "(" conditionalExpression ")" |
/// IDENTIFIER
Node _simpleExpression() {
var token = _scanner.next();
switch (token.type) {
case TokenType.not:
var child = _simpleExpression();
return NotNode(child, token.span.expand(child.span));
case TokenType.leftParen:
var child = _conditional();
if (!_scanner.scan(TokenType.rightParen)) {
throw SourceSpanFormatException(
'Expected ")".', _scanner.peek().span);
}
return child;
case TokenType.identifier:
return VariableNode((token as IdentifierToken).name, token.span);
default:
throw SourceSpanFormatException("Expected expression.", token.span);
}
}
}