// 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 = new 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 new 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 new SourceSpanFormatException(
          'Expected ":".', _scanner.peek().span);
    }

    var whenFalse = _conditional();
    return new ConditionalNode(condition, whenTrue, whenFalse);
  }

  /// Parses a logical or:
  ///
  ///     logicalOrExpression:
  ///       logicalAndExpression ("||" logicalOrExpression)?
  Node _or() {
    var left = _and();
    if (!_scanner.scan(TokenType.or)) return left;
    return new OrNode(left, _or());
  }

  /// Parses a logical and:
  ///
  ///     logicalAndExpression:
  ///       simpleExpression ("&&" logicalAndExpression)?
  Node _and() {
    var left = _simpleExpression();
    if (!_scanner.scan(TokenType.and)) return left;
    return new 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 new NotNode(child, token.span.expand(child.span));

      case TokenType.leftParen:
        var child = _conditional();
        if (!_scanner.scan(TokenType.rightParen)) {
          throw new SourceSpanFormatException(
              'Expected ")".', _scanner.peek().span);
        }
        return child;

      case TokenType.identifier:
        return new VariableNode((token as IdentifierToken).name, token.span);

      default:
        throw new SourceSpanFormatException("Expected expression.", token.span);
    }
  }
}
