| // 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. |
| std::string_view 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" |
| " " + |
| std::string(base_str) + " = " + std::string(base_str) + |
| "\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()) + "> " + |
| std::string(op_node->op().value()) + " <" + |
| 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: |
| case Value::SCOPE: { |
| 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; |
| |
| case Value::NONE: |
| 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 && written_value->type() == Value::LIST) { |
| 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(); |
| } |