content: // Copyright 2016 Google Inc. Use of this source code is governed by an
  // MIT-style license that can be found in the LICENSE file or at
  // https://opensource.org/licenses/MIT.
  
  import 'package:charcode/charcode.dart';
  import 'package:meta/meta.dart';
  import 'package:source_span/source_span.dart';
  
  import '../../../util/span.dart';
  import '../../../visitor/interface/expression.dart';
  import '../expression.dart';
  import 'list.dart';
  
  /// A binary operator, as in `1 + 2` or `$this and $other`.
  ///
  /// {@category AST}
  final class BinaryOperationExpression extends Expression {
    /// The operator being invoked.
    final BinaryOperator operator;
  
    /// The left-hand operand.
    final Expression left;
  
    /// The right-hand operand.
    final Expression right;
  
    /// Whether this is a [BinaryOperator.dividedBy] operation that may be
    /// interpreted as slash-separated numbers.
    ///
    /// @nodoc
    @internal
    final bool allowsSlash;
  
    FileSpan get span {
      // Avoid creating a bunch of intermediate spans for multiple binary
      // expressions in a row by moving to the left- and right-most expressions.
      var left = this.left;
      while (left is BinaryOperationExpression) {
        left = left.left;
      }
  
      var right = this.right;
      while (right is BinaryOperationExpression) {
        right = right.right;
      }
      return left.span.expand(right.span);
    }
  
    /// Returns the span that covers only [operator].
    ///
    /// @nodoc
    @internal
    FileSpan get operatorSpan => left.span.file == right.span.file &&
            left.span.end.offset < right.span.start.offset
        ? left.span.file
            .span(left.span.end.offset, right.span.start.offset)
            .trim()
        : span;
  
    BinaryOperationExpression(this.operator, this.left, this.right)
        : allowsSlash = false;
  
    /// Creates a [BinaryOperator.dividedBy] operation that may be interpreted as
    /// slash-separated numbers.
    ///
    /// @nodoc
    @internal
    BinaryOperationExpression.slash(this.left, this.right)
        : operator = BinaryOperator.dividedBy,
          allowsSlash = true;
  
    T accept<T>(ExpressionVisitor<T> visitor) =>
        visitor.visitBinaryOperationExpression(this);
  
    String toString() {
      var buffer = StringBuffer();
  
      // dart-lang/language#3064 and #3062 track potential ways of making this
      // cleaner.
      var leftNeedsParens = switch (left) {
        BinaryOperationExpression(operator: BinaryOperator(:var precedence)) =>
          precedence < operator.precedence,
        ListExpression(hasBrackets: false, contents: [_, _, ...]) => true,
        _ => false,
      };
      if (leftNeedsParens) buffer.writeCharCode($lparen);
      buffer.write(left);
      if (leftNeedsParens) buffer.writeCharCode($rparen);
  
      buffer.writeCharCode($space);
      buffer.write(operator.operator);
      buffer.writeCharCode($space);
  
      var right = this.right; // Hack to make analysis work.
      var rightNeedsParens = switch (right) {
        BinaryOperationExpression(:var operator) =>
          operator.precedence <= this.operator.precedence &&
              !(operator == this.operator && operator.isAssociative),
        ListExpression(hasBrackets: false, contents: [_, _, ...]) => true,
        _ => false,
      };
      if (rightNeedsParens) buffer.writeCharCode($lparen);
      buffer.write(right);
      if (rightNeedsParens) buffer.writeCharCode($rparen);
  
      return buffer.toString();
    }
  }
  
  /// A binary operator constant.
  ///
  /// {@category AST}
  enum BinaryOperator {
    // Note: When updating these operators, also update
    // pkg/sass-parser/lib/src/expression/binary-operation.ts.
  
    /// The Microsoft equals operator, `=`.
    singleEquals('single equals', '=', 0),
  
    /// The disjunction operator, `or`.
    or('or', 'or', 1, associative: true),
  
    /// The conjunction operator, `and`.
    and('and', 'and', 2, associative: true),
  
    /// The equality operator, `==`.
    equals('equals', '==', 3),
  
    /// The inequality operator, `!=`.
    notEquals('not equals', '!=', 3),
  
    /// The greater-than operator, `>`.
    greaterThan('greater than', '>', 4),
  
    /// The greater-than-or-equal-to operator, `>=`.
    greaterThanOrEquals('greater than or equals', '>=', 4),
  
    /// The less-than operator, `<`.
    lessThan('less than', '<', 4),
  
    /// The less-than-or-equal-to operator, `<=`.
    lessThanOrEquals('less than or equals', '<=', 4),
  
    /// The addition operator, `+`.
    plus('plus', '+', 5, associative: true),
  
    /// The subtraction operator, `-`.
    minus('minus', '-', 5),
  
    /// The multiplication operator, `*`.
    times('times', '*', 6, associative: true),
  
    /// The division operator, `/`.
    dividedBy('divided by', '/', 6),
  
    /// The modulo operator, `%`.
    modulo('modulo', '%', 6);
  
    /// The English name of `this`.
    final String name;
  
    /// The Sass syntax for `this`.
    final String operator;
  
    /// The precedence of `this`.
    ///
    /// An operator with higher precedence binds tighter.
    final int precedence;
  
    /// Whether this operation has the [associative property].
    ///
    /// [associative property]: https://en.wikipedia.org/wiki/Associative_property
    final bool isAssociative;
  
    const BinaryOperator(
      this.name,
      this.operator,
      this.precedence, {
      bool associative = false,
    }) : isAssociative = associative;
  
    String toString() => name;
  }
  