|  | // 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 "tools/gn/operators.h" | 
|  |  | 
|  | #include <stddef.h> | 
|  | #include <algorithm> | 
|  |  | 
|  | #include "base/strings/string_number_conversions.h" | 
|  | #include "tools/gn/err.h" | 
|  | #include "tools/gn/parse_tree.h" | 
|  | #include "tools/gn/scope.h" | 
|  | #include "tools/gn/token.h" | 
|  | #include "tools/gn/value.h" | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | const char kSourcesName[] = "sources"; | 
|  |  | 
|  | // 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 the sources assignment filter if it exists for the current | 
|  | // scope and it should be applied to this assignment. Otherwise returns null. | 
|  | const PatternList* GetAssignmentFilter(const Scope* exec_scope) const; | 
|  |  | 
|  | // 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. | 
|  | base::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; | 
|  | } | 
|  |  | 
|  | const PatternList* ValueDestination::GetAssignmentFilter( | 
|  | const Scope* exec_scope) const { | 
|  | if (type_ != SCOPE) | 
|  | return nullptr;  // Destination can't be named, so no sources filtering. | 
|  | if (name_token_->value() != kSourcesName) | 
|  | return nullptr;  // Destination not named "sources". | 
|  |  | 
|  | const PatternList* filter = exec_scope->GetSourcesAssignmentFilter(); | 
|  | if (!filter || filter->is_empty()) | 
|  | return nullptr;  // No filter or empty filter, don't need to do anything. | 
|  | return filter; | 
|  | } | 
|  |  | 
|  | 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. | 
|  | DCHECK(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 { | 
|  | NOTREACHED(); | 
|  | } | 
|  |  | 
|  | 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(); | 
|  | } | 
|  | } | 
|  |  | 
|  | Value* written_value = dest->SetValue(std::move(right), op_node->right()); | 
|  |  | 
|  | // Optionally apply the assignment filter in-place. | 
|  | const PatternList* filter = dest->GetAssignmentFilter(exec_scope); | 
|  | if (filter) { | 
|  | std::vector<Value>& list_value = written_value->list_value(); | 
|  | auto first_deleted = std::remove_if( | 
|  | list_value.begin(), list_value.end(), | 
|  | [filter](const Value& v) { return filter->MatchesValue(v); }); | 
|  | list_value.erase(first_deleted, list_value.end()); | 
|  | } | 
|  | 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, base::Int64ToString(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() + | 
|  | base::Int64ToString(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;  // FIXME(brettw) des this copy? | 
|  | } | 
|  | *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;  // FIXME(brettw) does this copy? | 
|  | } | 
|  |  | 
|  | *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( | 
|  | base::Int64ToString(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. | 
|  | const PatternList* filter = dest->GetAssignmentFilter(exec_scope); | 
|  | if (filter) { | 
|  | // Filtered list concat. | 
|  | for (Value& value : right.list_value()) { | 
|  | if (!filter->MatchesValue(value)) | 
|  | mutable_dest->list_value().push_back(std::move(value)); | 
|  | } | 
|  | } else { | 
|  | // 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) { | 
|  | DCHECK(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(); | 
|  | } | 
|  | // TODO(scottmg): Why no unary minus? | 
|  | 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 { | 
|  | NOTREACHED(); | 
|  | } | 
|  | 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(); | 
|  | } |