text: // Copyright (c) 2013 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "icl/operators.h"
  
  #include <assert.h>
  #include <stddef.h>
  
  #include <algorithm>
  
  #include "icl/err.h"
  #include "icl/parse_tree.h"
  #include "icl/scope.h"
  #include "icl/string_number_conversions.h"
  #include "icl/token.h"
  #include "icl/value.h"
  
  namespace icl {
  
  namespace {
  
  // Helper class used for assignment operations: =, +=, and -= to generalize
  // writing to various types of destinations.
  class ValueDestination {
   public:
    ValueDestination();
  
    bool Init(Scope* exec_scope,
              const ParseNode* dest,
              const BinaryOpNode* op_node,
              Err* err);
  
    // Returns the value in the destination scope if it already exists, or null
    // if it doesn't. This is for validation and does not count as a "use".
    // Other nested scopes will be searched.
    const Value* GetExistingValue() const;
  
    // Returns an existing version of the output if it can be modified. This will
    // not search nested scopes since writes only go into the current scope.
    // Returns null if the value does not exist, or is not in the current scope
    // (meaning assignments won't go to this value and it's not mutable). This
    // is for implementing += and -=.
    //
    // If it exists, this will mark the origin of the value to be the passed-in
    // node, and the value will be also marked unused (if possible) under the
    // assumption that it will be modified in-place.
    Value* GetExistingMutableValueIfExists(const ParseNode* origin);
  
    // Returns a pointer to the value that was set.
    Value* SetValue(Value value, const ParseNode* set_node);
  
    // Fills the Err with an undefined value error appropriate for modification
    // operators: += and -= (where the source is also the dest).
    void MakeUndefinedIdentifierForModifyError(Err* err);
  
   private:
    enum Type { UNINITIALIZED, SCOPE, LIST };
  
    Type type_;
  
    // Valid when type_ == SCOPE.
    Scope* scope_;
    const Token* name_token_;
  
    // Valid when type_ == LIST.
    Value* list_;
    size_t index_;  // Guaranteed in-range when Init() succeeds.
  };
  
  ValueDestination::ValueDestination()
      : type_(UNINITIALIZED),
        scope_(nullptr),
        name_token_(nullptr),
        list_(nullptr),
        index_(0) {
  }
  
  bool ValueDestination::Init(Scope* exec_scope,
                              const ParseNode* dest,
                              const BinaryOpNode* op_node,
                              Err* err) {
    // Check for standard variable set.
    const IdentifierNode* dest_identifier = dest->AsIdentifier();
    if (dest_identifier) {
      type_ = SCOPE;
      scope_ = exec_scope;
      name_token_ = &dest_identifier->value();
      return true;
    }
  
    // Check for array and scope accesses. The base (array or scope variable
    // name) must always be defined ahead of time.
    const AccessorNode* dest_accessor = dest->AsAccessor();
    if (!dest_accessor) {
      *err = Err(op_node, "Assignment requires a lvalue.",
                 "This thing on the left is not an identifier or accessor.");
      err->AppendRange(dest->GetRange());
      return false;
    }
  
    // Known to be an accessor.
    StringPiece base_str = dest_accessor->base().value();
    Value* base = exec_scope->GetMutableValue(
        base_str, Scope::SEARCH_CURRENT, false);
    if (!base) {
      // Base is either undefined or it's defined but not in the current scope.
      // Make a good error message.
      if (exec_scope->GetValue(base_str, false)) {
        *err = Err(dest_accessor->base(), "Suspicious in-place modification.",
            "This variable exists in a containing scope. Normally, writing to it "
            "would\nmake a copy of it into the current scope with the modified "
            "version. But\nhere you're modifying only an element of a scope or "
            "list object. It's unlikely\nyou meant to copy the entire thing just "
            "to modify this part of it.\n"
            "\n"
            "If you really wanted to do this, do:\n"
            "  " + base_str.as_string() + " = " + base_str.as_string() + "\n"
            "to copy it into the current scope before doing this operation.");
      } else {
        *err = Err(dest_accessor->base(), "Undefined identifier.");
      }
      return false;
    }
  
    if (dest_accessor->index()) {
      // List access with an index.
      if (!base->VerifyTypeIs(Value::LIST, err)) {
        // Errors here will confusingly refer to the variable declaration (since
        // that's all Value knows) rather than the list access. So rewrite the
        // error location to refer to the base value's location.
        *err = Err(dest_accessor->base(), err->message(), err->help_text());
        return false;
      }
  
      type_ = LIST;
      list_ = base;
      return dest_accessor->ComputeAndValidateListIndex(
          exec_scope, base->list_value().size(), &index_, err);
    }
  
    // Scope access with a dot.
    if (!base->VerifyTypeIs(Value::SCOPE, err)) {
      // As the for the list index case above, rewrite the error location.
      *err = Err(dest_accessor->base(), err->message(), err->help_text());
      return false;
    }
    type_ = SCOPE;
    scope_ = base->scope_value();
    name_token_ = &dest_accessor->member()->value();
    return true;
  }
  
  const Value* ValueDestination::GetExistingValue() const {
    if (type_ == SCOPE)
      return scope_->GetValue(name_token_->value(), true);
    else if (type_ == LIST)
      return &list_->list_value()[index_];
    return nullptr;
  }
  
  Value* ValueDestination::GetExistingMutableValueIfExists(
      const ParseNode* origin) {
    if (type_ == SCOPE) {
      Value* value = scope_->GetMutableValue(
          name_token_->value(), Scope::SEARCH_CURRENT, false);
      if (value) {
        // The value will be written to, reset its tracking information.
        value->set_origin(origin);
        scope_->MarkUnused(name_token_->value());
      }
    }
    if (type_ == LIST)
      return &list_->list_value()[index_];
    return nullptr;
  }
  
  Value* ValueDestination::SetValue(Value value, const ParseNode* set_node) {
    if (type_ == SCOPE) {
      return scope_->SetValue(name_token_->value(), std::move(value), set_node);
    } else if (type_ == LIST) {
      Value* dest = &list_->list_value()[index_];
      *dest = std::move(value);
      return dest;
    }
    return nullptr;
  }
  
  void ValueDestination::MakeUndefinedIdentifierForModifyError(Err* err) {
    // When Init() succeeds, the base of any accessor has already been resolved
    // and that list indices are in-range. This means any undefined identifiers
    // are for scope accesses.
    assert(type_ == SCOPE);
    *err = Err(*name_token_, "Undefined identifier.");
  }
  
  // Computes an error message for overwriting a nonempty list/scope with another.
  Err MakeOverwriteError(const BinaryOpNode* op_node,
                         const Value& old_value) {
    std::string type_name;
    std::string empty_def;
  
    if (old_value.type() == Value::LIST) {
      type_name = "list";
      empty_def = "[]";
    } else if (old_value.type() == Value::SCOPE) {
      type_name = "scope";
      empty_def = "{}";
    } else {
      assert(false);
    }
  
    Err result(op_node->left()->GetRange(),
        "Replacing nonempty " + type_name + ".",
        "This overwrites a previously-defined nonempty " + type_name +
        "with another nonempty " + type_name + ".");
    result.AppendSubErr(Err(old_value, "for previous definition",
        "Did you mean to append/modify instead? If you really want to overwrite, "
        "do:\n"
        "  foo = " + empty_def + "\nbefore reassigning."));
    return result;
  }
  
  // -----------------------------------------------------------------------------
  
  Err MakeIncompatibleTypeError(const BinaryOpNode* op_node,
                                const Value& left,
                                const Value& right) {
    std::string msg =
        std::string("You can't do <") + Value::DescribeType(left.type()) + "> " +
        op_node->op().value().as_string() +
        " <" + Value::DescribeType(right.type()) + ">.";
    if (left.type() == Value::LIST) {
      // Append extra hint for list stuff.
      msg += "\n\nHint: If you're attempting to add or remove a single item from "
          " a list, use \"foo + [ bar ]\".";
    }
    return Err(op_node, "Incompatible types for binary operator.", msg);
  }
  
  Value GetValueOrFillError(const BinaryOpNode* op_node,
                            const ParseNode* node,
                            const char* name,
                            Scope* scope,
                            Err* err) {
    Value value = node->Execute(scope, err);
    if (err->has_error())
      return Value();
    if (value.type() == Value::NONE) {
      *err = Err(op_node->op(),
                 "Operator requires a value.",
                 "This thing on the " + std::string(name) +
                     " does not evaluate to a value.");
      err->AppendRange(node->GetRange());
      return Value();
    }
    return value;
  }
  
  void RemoveMatchesFromList(const BinaryOpNode* op_node,
                             Value* list,
                             const Value& to_remove,
                             Err* err) {
    std::vector<Value>& v = list->list_value();
    switch (to_remove.type()) {
      case Value::BOOLEAN:
      case Value::INTEGER:  // Filter out the individual int/string.
      case Value::STRING: {
        bool found_match = false;
        for (size_t i = 0; i < v.size(); /* nothing */) {
          if (v[i] == to_remove) {
            found_match = true;
            v.erase(v.begin() + i);
          } else {
            i++;
          }
        }
        if (!found_match) {
          *err = Err(to_remove.origin()->GetRange(), "Item not found",
              "You were trying to remove " + to_remove.ToString(true) +
              "\nfrom the list but it wasn't there.");
        }
        break;
      }
  
      case Value::LIST:  // Filter out each individual thing.
        for (const auto& elem : to_remove.list_value()) {
          // TODO(brettw) if the nested item is a list, we may want to search
          // for the literal list rather than remote the items in it.
          RemoveMatchesFromList(op_node, list, elem, err);
          if (err->has_error())
            return;
        }
        break;
  
      default:
        break;
    }
  }
  
  // Assignment -----------------------------------------------------------------
  
  // We return a null value from this rather than the result of doing the append.
  // See ValuePlusEquals for rationale.
  Value ExecuteEquals(Scope* exec_scope,
                      const BinaryOpNode* op_node,
                      ValueDestination* dest,
                      Value right,
                      Err* err) {
    const Value* old_value = dest->GetExistingValue();
    if (old_value) {
      // Check for overwriting nonempty scopes or lists with other nonempty
      // scopes or lists. This prevents mistakes that clobber a value rather than
      // appending to it. For cases where a user meant to clear a value, allow
      // overwriting a nonempty list/scope with an empty one, which can then be
      // modified.
      if (old_value->type() == Value::LIST && right.type() == Value::LIST &&
          !old_value->list_value().empty() && !right.list_value().empty()) {
        *err = MakeOverwriteError(op_node, *old_value);
        return Value();
      } else if (old_value->type() == Value::SCOPE &&
                 right.type() == Value::SCOPE &&
                 old_value->scope_value()->HasValues(Scope::SEARCH_CURRENT) &&
                 right.scope_value()->HasValues(Scope::SEARCH_CURRENT)) {
        *err = MakeOverwriteError(op_node, *old_value);
        return Value();
      }
    }
  
    dest->SetValue(std::move(right), op_node->right());
    return Value();
  }
  
  // Plus/minus ------------------------------------------------------------------
  
  // allow_left_type_conversion indicates if we're allowed to change the type of
  // the left value. This is set to true when doing +, and false when doing +=.
  Value ExecutePlus(const BinaryOpNode* op_node,
                    Value left,
                    Value right,
                    bool allow_left_type_conversion,
                    Err* err) {
    // Left-hand-side integer.
    if (left.type() == Value::INTEGER) {
      if (right.type() == Value::INTEGER) {
        // Int + int -> addition.
        return Value(op_node, left.int_value() + right.int_value());
      } else if (right.type() == Value::STRING && allow_left_type_conversion) {
        // Int + string -> string concat.
        return Value(
            op_node,
            NumberToString<int64_t>(left.int_value()) + right.string_value());
      }
      *err = MakeIncompatibleTypeError(op_node, left, right);
      return Value();
    }
  
    // Left-hand-side string.
    if (left.type() == Value::STRING) {
      if (right.type() == Value::INTEGER) {
        // String + int -> string concat.
        return Value(op_node,
            left.string_value() + NumberToString<int64_t>(right.int_value()));
      } else if (right.type() == Value::STRING) {
        // String + string -> string concat. Since the left is passed by copy
        // we can avoid realloc if there is enough buffer by appending to left
        // and assigning.
        left.string_value().append(right.string_value());
        return left;
      }
      *err = MakeIncompatibleTypeError(op_node, left, right);
      return Value();
    }
  
    // Left-hand-side list. The only valid thing is to add another list.
    if (left.type() == Value::LIST && right.type() == Value::LIST) {
      // Since left was passed by copy, avoid realloc by destructively appending
      // to it and using that as the result.
      for (Value& value : right.list_value())
        left.list_value().push_back(std::move(value));
      return left;
    }
  
    *err = MakeIncompatibleTypeError(op_node, left, right);
    return Value();
  }
  
  // Left is passed by value because it will be modified in-place and returned
  // for the list case.
  Value ExecuteMinus(const BinaryOpNode* op_node,
                     Value left,
                     const Value& right,
                     Err* err) {
    // Left-hand-side int. The only thing to do is subtract another int.
    if (left.type() == Value::INTEGER && right.type() == Value::INTEGER) {
      // Int - int -> subtraction.
      return Value(op_node, left.int_value() - right.int_value());
    }
  
    // Left-hand-side list. The only thing to do is subtract another list.
    if (left.type() == Value::LIST && right.type() == Value::LIST) {
      // In-place modify left and return it.
      RemoveMatchesFromList(op_node, &left, right, err);
      return left;
    }
  
    *err = MakeIncompatibleTypeError(op_node, left, right);
    return Value();
  }
  
  // In-place plus/minus ---------------------------------------------------------
  
  void ExecutePlusEquals(Scope* exec_scope,
                         const BinaryOpNode* op_node,
                         ValueDestination* dest,
                         Value right,
                         Err* err) {
    // There are several cases. Some things we can convert "foo += bar" to
    // "foo = foo + bar". Some cases we can't (the 'sources' variable won't
    // get the right filtering on the list). Some cases we don't want to (lists
    // and strings will get unnecessary copying so we can to optimize these).
    //
    //  - Value is already mutable in the current scope:
    //     1. List/string append: use it.
    //     2. Other types: fall back to "foo = foo + bar"
    //
    //  - Value is not mutable in the current scope:
    //     3. List/string append: copy into current scope and append to that.
    //     4. Other types: fall back to "foo = foo + bar"
    //
    // The common case is to use += for list and string appends in the local
    // scope, so this is written to avoid multiple variable lookups in that case.
    Value* mutable_dest = dest->GetExistingMutableValueIfExists(op_node);
    if (!mutable_dest) {
      const Value* existing_value = dest->GetExistingValue();
      if (!existing_value) {
        // Undefined left-hand-size for +=.
        dest->MakeUndefinedIdentifierForModifyError(err);
        return;
      }
  
      if (existing_value->type() != Value::STRING &&
          existing_value->type() != Value::LIST) {
        // Case #4 above.
        dest->SetValue(ExecutePlus(op_node, *existing_value,
                                   std::move(right), false, err), op_node);
        return;
      }
  
      // Case #3 above, copy to current scope and fall-through to appending.
      mutable_dest = dest->SetValue(*existing_value, op_node);
    } else if (mutable_dest->type() != Value::STRING &&
               mutable_dest->type() != Value::LIST) {
      // Case #2 above.
      dest->SetValue(ExecutePlus(op_node, *mutable_dest,
                                 std::move(right), false, err), op_node);
      return;
    }  // "else" is case #1 above.
  
    if (mutable_dest->type() == Value::STRING) {
      if (right.type() == Value::INTEGER) {
        // String + int -> string concat.
        mutable_dest->string_value().append(
            NumberToString<int64_t>(right.int_value()));
      } else if (right.type() == Value::STRING) {
        // String + string -> string concat.
        mutable_dest->string_value().append(right.string_value());
      } else {
        *err = MakeIncompatibleTypeError(op_node, *mutable_dest, right);
      }
    } else if (mutable_dest->type() == Value::LIST) {
      // List concat.
      if (right.type() == Value::LIST) {
        // Note: don't reserve() the dest vector here since that actually hurts
        // the allocation pattern when the build script is doing multiple small
        // additions.
        // Normal list concat. This is a destructive move.
        for (Value& value : right.list_value())
          mutable_dest->list_value().push_back(std::move(value));
      } else {
        *err = Err(op_node->op(), "Incompatible types to add.",
            "To append a single item to a list do \"foo += [ bar ]\".");
      }
    }
  }
  
  void ExecuteMinusEquals(const BinaryOpNode* op_node,
                          ValueDestination* dest,
                          const Value& right,
                          Err* err) {
    // Like the += case, we can convert "foo -= bar" to "foo = foo - bar". Since
    // there is no sources filtering, this is always semantically valid. The
    // only case we don't do it is for lists in the current scope which is the
    // most common case, and also the one that can be optimized the most by
    // doing it in-place.
    Value* mutable_dest = dest->GetExistingMutableValueIfExists(op_node);
    if (!mutable_dest ||
        (mutable_dest->type() != Value::LIST || right.type() != Value::LIST)) {
      const Value* existing_value = dest->GetExistingValue();
      if (!existing_value) {
        // Undefined left-hand-size for -=.
        dest->MakeUndefinedIdentifierForModifyError(err);
        return;
      }
      dest->SetValue(ExecuteMinus(op_node, *existing_value, right, err), op_node);
      return;
    }
  
    // In-place removal of items from "right".
    RemoveMatchesFromList(op_node, mutable_dest, right, err);
  }
  
  // Comparison -----------------------------------------------------------------
  
  Value ExecuteEqualsEquals(Scope* scope,
                            const BinaryOpNode* op_node,
                            const Value& left,
                            const Value& right,
                            Err* err) {
    if (left == right)
      return Value(op_node, true);
    return Value(op_node, false);
  }
  
  Value ExecuteNotEquals(Scope* scope,
                         const BinaryOpNode* op_node,
                         const Value& left,
                         const Value& right,
                         Err* err) {
    // Evaluate in terms of ==.
    Value result = ExecuteEqualsEquals(scope, op_node, left, right, err);
    result.boolean_value() = !result.boolean_value();
    return result;
  }
  
  Value FillNeedsTwoIntegersError(const BinaryOpNode* op_node,
                                  const Value& left,
                                  const Value& right,
                                  Err* err) {
    *err = Err(op_node, "Comparison requires two integers.",
               "This operator can only compare two integers.");
    err->AppendRange(left.origin()->GetRange());
    err->AppendRange(right.origin()->GetRange());
    return Value();
  }
  
  Value ExecuteLessEquals(Scope* scope,
                          const BinaryOpNode* op_node,
                          const Value& left,
                          const Value& right,
                          Err* err) {
    if (left.type() != Value::INTEGER || right.type() != Value::INTEGER)
      return FillNeedsTwoIntegersError(op_node, left, right, err);
    return Value(op_node, left.int_value() <= right.int_value());
  }
  
  Value ExecuteGreaterEquals(Scope* scope,
                             const BinaryOpNode* op_node,
                             const Value& left,
                             const Value& right,
                             Err* err) {
    if (left.type() != Value::INTEGER || right.type() != Value::INTEGER)
      return FillNeedsTwoIntegersError(op_node, left, right, err);
    return Value(op_node, left.int_value() >= right.int_value());
  }
  
  Value ExecuteGreater(Scope* scope,
                       const BinaryOpNode* op_node,
                       const Value& left,
                       const Value& right,
                       Err* err) {
    if (left.type() != Value::INTEGER || right.type() != Value::INTEGER)
      return FillNeedsTwoIntegersError(op_node, left, right, err);
    return Value(op_node, left.int_value() > right.int_value());
  }
  
  Value ExecuteLess(Scope* scope,
                    const BinaryOpNode* op_node,
                    const Value& left,
                    const Value& right,
                    Err* err) {
    if (left.type() != Value::INTEGER || right.type() != Value::INTEGER)
      return FillNeedsTwoIntegersError(op_node, left, right, err);
    return Value(op_node, left.int_value() < right.int_value());
  }
  
  // Binary ----------------------------------------------------------------------
  
  Value ExecuteOr(Scope* scope,
                  const BinaryOpNode* op_node,
                  const ParseNode* left_node,
                  const ParseNode* right_node,
                  Err* err) {
    Value left = GetValueOrFillError(op_node, left_node, "left", scope, err);
    if (err->has_error())
      return Value();
    if (left.type() != Value::BOOLEAN) {
      *err = Err(op_node->left(), "Left side of || operator is not a boolean.",
          "Type is \"" + std::string(Value::DescribeType(left.type())) +
          "\" instead.");
      return Value();
    }
    if (left.boolean_value())
      return Value(op_node, left.boolean_value());
  
    Value right = GetValueOrFillError(op_node, right_node, "right", scope, err);
    if (err->has_error())
      return Value();
    if (right.type() != Value::BOOLEAN) {
      *err = Err(op_node->right(), "Right side of || operator is not a boolean.",
          "Type is \"" + std::string(Value::DescribeType(right.type())) +
          "\" instead.");
      return Value();
    }
  
    return Value(op_node, left.boolean_value() || right.boolean_value());
  }
  
  Value ExecuteAnd(Scope* scope,
                   const BinaryOpNode* op_node,
                   const ParseNode* left_node,
                   const ParseNode* right_node,
                   Err* err) {
    Value left = GetValueOrFillError(op_node, left_node, "left", scope, err);
    if (err->has_error())
      return Value();
    if (left.type() != Value::BOOLEAN) {
      *err = Err(op_node->left(), "Left side of && operator is not a boolean.",
          "Type is \"" + std::string(Value::DescribeType(left.type())) +
          "\" instead.");
      return Value();
    }
    if (!left.boolean_value())
      return Value(op_node, left.boolean_value());
  
    Value right = GetValueOrFillError(op_node, right_node, "right", scope, err);
    if (err->has_error())
      return Value();
    if (right.type() != Value::BOOLEAN) {
      *err = Err(op_node->right(), "Right side of && operator is not a boolean.",
          "Type is \"" + std::string(Value::DescribeType(right.type())) +
          "\" instead.");
      return Value();
    }
    return Value(op_node, left.boolean_value() && right.boolean_value());
  }
  
  }  // namespace
  
  // ----------------------------------------------------------------------------
  
  Value ExecuteUnaryOperator(Scope* scope,
                             const UnaryOpNode* op_node,
                             const Value& expr,
                             Err* err) {
    if (op_node->op().type() == Token::MINUS) {
      if (expr.type() != Value::INTEGER) {
        *err = Err(op_node, "Operand of unary - operator is not an integer.",
            "Type is \"" + std::string(Value::DescribeType(expr.type())) +
            "\" instead.");
        return Value();
      }
      // TODO(vtl): Maybe we should check overflow? (Elsewhere too.)
      return Value(op_node, -expr.int_value());
    }
  
    assert(op_node->op().type() == Token::BANG);
    if (expr.type() != Value::BOOLEAN) {
      *err = Err(op_node, "Operand of ! operator is not a boolean.",
          "Type is \"" + std::string(Value::DescribeType(expr.type())) +
          "\" instead.");
      return Value();
    }
    return Value(op_node, !expr.boolean_value());
  }
  
  Value ExecuteBinaryOperator(Scope* scope,
                              const BinaryOpNode* op_node,
                              const ParseNode* left,
                              const ParseNode* right,
                              Err* err) {
    const Token& op = op_node->op();
  
    // First handle the ones that take an lvalue.
    if (op.type() == Token::EQUAL ||
        op.type() == Token::PLUS_EQUALS ||
        op.type() == Token::MINUS_EQUALS) {
      // Compute the left side.
      ValueDestination dest;
      if (!dest.Init(scope, left, op_node, err))
        return Value();
  
      // Compute the right side.
      Value right_value = right->Execute(scope, err);
      if (err->has_error())
        return Value();
      if (right_value.type() == Value::NONE) {
        *err = Err(op, "Operator requires a rvalue.",
                   "This thing on the right does not evaluate to a value.");
        err->AppendRange(right->GetRange());
        return Value();
      }
  
      // "foo += bar" (same for "-=") is converted to "foo = foo + bar" here, but
      // we pass the original value of "foo" by pointer to avoid a copy.
      if (op.type() == Token::EQUAL) {
        ExecuteEquals(scope, op_node, &dest, std::move(right_value), err);
      } else if (op.type() == Token::PLUS_EQUALS) {
        ExecutePlusEquals(scope, op_node, &dest, std::move(right_value), err);
      } else if (op.type() == Token::MINUS_EQUALS) {
        ExecuteMinusEquals(op_node, &dest, right_value, err);
      } else {
        assert(false);
      }
      return Value();
    }
  
    // ||, &&. Passed the node instead of the value so that they can avoid
    // evaluating the RHS on early-out.
    if (op.type() == Token::BOOLEAN_OR)
      return ExecuteOr(scope, op_node, left, right, err);
    if (op.type() == Token::BOOLEAN_AND)
      return ExecuteAnd(scope, op_node, left, right, err);
  
    // Everything else works on the evaluated left and right values.
    Value left_value = GetValueOrFillError(op_node, left, "left", scope, err);
    if (err->has_error())
      return Value();
    Value right_value = GetValueOrFillError(op_node, right, "right", scope, err);
    if (err->has_error())
      return Value();
  
    // +, -.
    if (op.type() == Token::MINUS)
      return ExecuteMinus(op_node, std::move(left_value), right_value, err);
    if (op.type() == Token::PLUS) {
      return ExecutePlus(op_node, std::move(left_value), std::move(right_value),
                         true, err);
    }
  
    // Comparisons.
    if (op.type() == Token::EQUAL_EQUAL)
      return ExecuteEqualsEquals(scope, op_node, left_value, right_value, err);
    if (op.type() == Token::NOT_EQUAL)
      return ExecuteNotEquals(scope, op_node, left_value, right_value, err);
    if (op.type() == Token::GREATER_EQUAL)
      return ExecuteGreaterEquals(scope, op_node, left_value, right_value, err);
    if (op.type() == Token::LESS_EQUAL)
      return ExecuteLessEquals(scope, op_node, left_value, right_value, err);
    if (op.type() == Token::GREATER_THAN)
      return ExecuteGreater(scope, op_node, left_value, right_value, err);
    if (op.type() == Token::LESS_THAN)
      return ExecuteLess(scope, op_node, left_value, right_value, err);
  
    return Value();
  }
  
  }  // namespace icl
  