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