// 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 #include #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& 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& 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(); }