| // 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/parse_tree.h" | |
| #include <stdint.h> | |
| #include <memory> | |
| #include <string> | |
| #include <tuple> | |
| #include "base/json/string_escape.h" | |
| #include "base/stl_util.h" | |
| #include "base/strings/string_number_conversions.h" | |
| #include "tools/gn/functions.h" | |
| #include "tools/gn/operators.h" | |
| #include "tools/gn/scope.h" | |
| #include "tools/gn/string_utils.h" | |
| // Dictionary keys used for JSON-formatted tree dump. | |
| const char kJsonNodeChild[] = "child"; | |
| const char kJsonNodeType[] = "type"; | |
| const char kJsonNodeValue[] = "value"; | |
| const char kJsonBeforeComment[] = "before_comment"; | |
| const char kJsonSuffixComment[] = "suffix_comment"; | |
| const char kJsonAfterComment[] = "after_comment"; | |
| namespace { | |
| enum DepsCategory { | |
| DEPS_CATEGORY_LOCAL, | |
| DEPS_CATEGORY_RELATIVE, | |
| DEPS_CATEGORY_ABSOLUTE, | |
| DEPS_CATEGORY_OTHER, | |
| }; | |
| DepsCategory GetDepsCategory(base::StringPiece deps) { | |
| if (deps.length() < 2 || deps[0] != '"' || deps[deps.size() - 1] != '"') | |
| return DEPS_CATEGORY_OTHER; | |
| if (deps[1] == ':') | |
| return DEPS_CATEGORY_LOCAL; | |
| if (deps[1] == '/') | |
| return DEPS_CATEGORY_ABSOLUTE; | |
| return DEPS_CATEGORY_RELATIVE; | |
| } | |
| std::tuple<base::StringPiece, base::StringPiece> SplitAtFirst( | |
| base::StringPiece str, | |
| char c) { | |
| if (!str.starts_with("\"") || !str.ends_with("\"")) | |
| return std::make_tuple(str, base::StringPiece()); | |
| str = str.substr(1, str.length() - 2); | |
| size_t index_of_first = str.find(c); | |
| return std::make_tuple(str.substr(0, index_of_first), | |
| index_of_first != base::StringPiece::npos | |
| ? str.substr(index_of_first + 1) | |
| : base::StringPiece()); | |
| } | |
| bool IsSortRangeSeparator(const ParseNode* node, const ParseNode* prev) { | |
| // If it's a block comment, or has an attached comment with a blank line | |
| // before it, then we break the range at this point. | |
| return node->AsBlockComment() != nullptr || | |
| (prev && node->comments() && !node->comments()->before().empty() && | |
| (node->GetRange().begin().line_number() > | |
| prev->GetRange().end().line_number() + | |
| static_cast<int>(node->comments()->before().size() + 1))); | |
| } | |
| base::StringPiece GetStringRepresentation(const ParseNode* node) { | |
| DCHECK(node->AsLiteral() || node->AsIdentifier() || node->AsAccessor()); | |
| if (node->AsLiteral()) | |
| return node->AsLiteral()->value().value(); | |
| else if (node->AsIdentifier()) | |
| return node->AsIdentifier()->value().value(); | |
| else if (node->AsAccessor()) | |
| return node->AsAccessor()->base().value(); | |
| return base::StringPiece(); | |
| } | |
| } // namespace | |
| Comments::Comments() = default; | |
| Comments::~Comments() = default; | |
| void Comments::ReverseSuffix() { | |
| for (int i = 0, j = static_cast<int>(suffix_.size() - 1); i < j; ++i, --j) | |
| std::swap(suffix_[i], suffix_[j]); | |
| } | |
| ParseNode::ParseNode() = default; | |
| ParseNode::~ParseNode() = default; | |
| const AccessorNode* ParseNode::AsAccessor() const { | |
| return nullptr; | |
| } | |
| const BinaryOpNode* ParseNode::AsBinaryOp() const { | |
| return nullptr; | |
| } | |
| const BlockCommentNode* ParseNode::AsBlockComment() const { | |
| return nullptr; | |
| } | |
| const BlockNode* ParseNode::AsBlock() const { | |
| return nullptr; | |
| } | |
| const ConditionNode* ParseNode::AsConditionNode() const { | |
| return nullptr; | |
| } | |
| const EndNode* ParseNode::AsEnd() const { | |
| return nullptr; | |
| } | |
| const FunctionCallNode* ParseNode::AsFunctionCall() const { | |
| return nullptr; | |
| } | |
| const IdentifierNode* ParseNode::AsIdentifier() const { | |
| return nullptr; | |
| } | |
| const ListNode* ParseNode::AsList() const { | |
| return nullptr; | |
| } | |
| const LiteralNode* ParseNode::AsLiteral() const { | |
| return nullptr; | |
| } | |
| const UnaryOpNode* ParseNode::AsUnaryOp() const { | |
| return nullptr; | |
| } | |
| Comments* ParseNode::comments_mutable() { | |
| if (!comments_) | |
| comments_ = std::make_unique<Comments>(); | |
| return comments_.get(); | |
| } | |
| base::Value ParseNode::CreateJSONNode(const char* type) const { | |
| base::Value dict(base::Value::Type::DICTIONARY); | |
| dict.SetKey(kJsonNodeType, base::Value(type)); | |
| AddCommentsJSONNodes(&dict); | |
| return dict; | |
| } | |
| base::Value ParseNode::CreateJSONNode(const char* type, | |
| const base::StringPiece& value) const { | |
| base::Value dict(base::Value::Type::DICTIONARY); | |
| dict.SetKey(kJsonNodeType, base::Value(type)); | |
| dict.SetKey(kJsonNodeValue, base::Value(value)); | |
| AddCommentsJSONNodes(&dict); | |
| return dict; | |
| } | |
| void ParseNode::AddCommentsJSONNodes(base::Value* out_value) const { | |
| if (comments_) { | |
| if (comments_->before().size()) { | |
| base::Value comment_values(base::Value::Type::LIST); | |
| for (const auto& token : comments_->before()) | |
| comment_values.GetList().push_back(base::Value(token.value())); | |
| out_value->SetKey(kJsonBeforeComment, std::move(comment_values)); | |
| } | |
| if (comments_->suffix().size()) { | |
| base::Value comment_values(base::Value::Type::LIST); | |
| for (const auto& token : comments_->suffix()) | |
| comment_values.GetList().push_back(base::Value(token.value())); | |
| out_value->SetKey(kJsonSuffixComment, std::move(comment_values)); | |
| } | |
| if (comments_->after().size()) { | |
| base::Value comment_values(base::Value::Type::LIST); | |
| for (const auto& token : comments_->after()) | |
| comment_values.GetList().push_back(base::Value(token.value())); | |
| out_value->SetKey(kJsonAfterComment, std::move(comment_values)); | |
| } | |
| } | |
| } | |
| // AccessorNode --------------------------------------------------------------- | |
| AccessorNode::AccessorNode() = default; | |
| AccessorNode::~AccessorNode() = default; | |
| const AccessorNode* AccessorNode::AsAccessor() const { | |
| return this; | |
| } | |
| Value AccessorNode::Execute(Scope* scope, Err* err) const { | |
| if (index_) | |
| return ExecuteArrayAccess(scope, err); | |
| else if (member_) | |
| return ExecuteScopeAccess(scope, err); | |
| NOTREACHED(); | |
| return Value(); | |
| } | |
| LocationRange AccessorNode::GetRange() const { | |
| if (index_) | |
| return LocationRange(base_.location(), index_->GetRange().end()); | |
| else if (member_) | |
| return LocationRange(base_.location(), member_->GetRange().end()); | |
| NOTREACHED(); | |
| return LocationRange(); | |
| } | |
| Err AccessorNode::MakeErrorDescribing(const std::string& msg, | |
| const std::string& help) const { | |
| return Err(GetRange(), msg, help); | |
| } | |
| base::Value AccessorNode::GetJSONNode() const { | |
| base::Value dict(CreateJSONNode("ACCESSOR", base_.value())); | |
| base::Value child(base::Value::Type::LIST); | |
| if (index_) | |
| child.GetList().push_back(index_->GetJSONNode()); | |
| else if (member_) | |
| child.GetList().push_back(member_->GetJSONNode()); | |
| dict.SetKey(kJsonNodeChild, std::move(child)); | |
| return dict; | |
| } | |
| Value AccessorNode::ExecuteArrayAccess(Scope* scope, Err* err) const { | |
| const Value* base_value = scope->GetValue(base_.value(), true); | |
| if (!base_value) { | |
| *err = MakeErrorDescribing("Undefined identifier."); | |
| return Value(); | |
| } | |
| if (!base_value->VerifyTypeIs(Value::LIST, err)) | |
| return Value(); | |
| size_t index = 0; | |
| if (!ComputeAndValidateListIndex(scope, base_value->list_value().size(), | |
| &index, err)) | |
| return Value(); | |
| return base_value->list_value()[index]; | |
| } | |
| Value AccessorNode::ExecuteScopeAccess(Scope* scope, Err* err) const { | |
| // We jump through some hoops here since ideally a.b will count "b" as | |
| // accessed in the given scope. The value "a" might be in some normal nested | |
| // scope and we can modify it, but it might also be inherited from the | |
| // readonly root scope and we can't do used variable tracking on it. (It's | |
| // not legal to const cast it away since the root scope will be in readonly | |
| // mode and being accessed from multiple threads without locking.) So this | |
| // code handles both cases. | |
| const Value* result = nullptr; | |
| // Look up the value in the scope named by "base_". | |
| Value* mutable_base_value = | |
| scope->GetMutableValue(base_.value(), Scope::SEARCH_NESTED, true); | |
| if (mutable_base_value) { | |
| // Common case: base value is mutable so we can track variable accesses | |
| // for unused value warnings. | |
| if (!mutable_base_value->VerifyTypeIs(Value::SCOPE, err)) | |
| return Value(); | |
| result = mutable_base_value->scope_value()->GetValue( | |
| member_->value().value(), true); | |
| } else { | |
| // Fall back to see if the value is on a read-only scope. | |
| const Value* const_base_value = scope->GetValue(base_.value(), true); | |
| if (const_base_value) { | |
| // Read only value, don't try to mark the value access as a "used" one. | |
| if (!const_base_value->VerifyTypeIs(Value::SCOPE, err)) | |
| return Value(); | |
| result = | |
| const_base_value->scope_value()->GetValue(member_->value().value()); | |
| } else { | |
| *err = Err(base_, "Undefined identifier."); | |
| return Value(); | |
| } | |
| } | |
| if (!result) { | |
| *err = Err(member_.get(), "No value named \"" + member_->value().value() + | |
| "\" in scope \"" + base_.value() + "\""); | |
| return Value(); | |
| } | |
| return *result; | |
| } | |
| void AccessorNode::SetNewLocation(int line_number) { | |
| Location old = base_.location(); | |
| base_.set_location( | |
| Location(old.file(), line_number, old.column_number(), old.byte())); | |
| } | |
| bool AccessorNode::ComputeAndValidateListIndex(Scope* scope, | |
| size_t max_len, | |
| size_t* computed_index, | |
| Err* err) const { | |
| Value index_value = index_->Execute(scope, err); | |
| if (err->has_error()) | |
| return false; | |
| if (!index_value.VerifyTypeIs(Value::INTEGER, err)) | |
| return false; | |
| int64_t index_int = index_value.int_value(); | |
| if (index_int < 0) { | |
| *err = Err(index_->GetRange(), "Negative array subscript.", | |
| "You gave me " + base::Int64ToString(index_int) + "."); | |
| return false; | |
| } | |
| if (max_len == 0) { | |
| *err = Err(index_->GetRange(), "Array subscript out of range.", | |
| "You gave me " + base::Int64ToString(index_int) + " but the " + | |
| "array has no elements."); | |
| return false; | |
| } | |
| size_t index_sizet = static_cast<size_t>(index_int); | |
| if (index_sizet >= max_len) { | |
| *err = Err(index_->GetRange(), "Array subscript out of range.", | |
| "You gave me " + base::Int64ToString(index_int) + | |
| " but I was expecting something from 0 to " + | |
| base::NumberToString(max_len - 1) + ", inclusive."); | |
| return false; | |
| } | |
| *computed_index = index_sizet; | |
| return true; | |
| } | |
| // BinaryOpNode --------------------------------------------------------------- | |
| BinaryOpNode::BinaryOpNode() = default; | |
| BinaryOpNode::~BinaryOpNode() = default; | |
| const BinaryOpNode* BinaryOpNode::AsBinaryOp() const { | |
| return this; | |
| } | |
| Value BinaryOpNode::Execute(Scope* scope, Err* err) const { | |
| return ExecuteBinaryOperator(scope, this, left_.get(), right_.get(), err); | |
| } | |
| LocationRange BinaryOpNode::GetRange() const { | |
| return left_->GetRange().Union(right_->GetRange()); | |
| } | |
| Err BinaryOpNode::MakeErrorDescribing(const std::string& msg, | |
| const std::string& help) const { | |
| return Err(op_, msg, help); | |
| } | |
| base::Value BinaryOpNode::GetJSONNode() const { | |
| base::Value dict(CreateJSONNode("BINARY", op_.value())); | |
| base::Value child(base::Value::Type::LIST); | |
| child.GetList().push_back(left_->GetJSONNode()); | |
| child.GetList().push_back(right_->GetJSONNode()); | |
| dict.SetKey(kJsonNodeChild, std::move(child)); | |
| return dict; | |
| } | |
| // BlockNode ------------------------------------------------------------------ | |
| BlockNode::BlockNode(ResultMode result_mode) : result_mode_(result_mode) {} | |
| BlockNode::~BlockNode() = default; | |
| const BlockNode* BlockNode::AsBlock() const { | |
| return this; | |
| } | |
| Value BlockNode::Execute(Scope* enclosing_scope, Err* err) const { | |
| std::unique_ptr<Scope> nested_scope; // May be null. | |
| Scope* execution_scope; // Either the enclosing_scope or nested_scope. | |
| if (result_mode_ == RETURNS_SCOPE) { | |
| // Create a nested scope to save the values for returning. | |
| nested_scope = std::make_unique<Scope>(enclosing_scope); | |
| execution_scope = nested_scope.get(); | |
| } else { | |
| // Use the enclosing scope. Modifications will go into this also (for | |
| // example, if conditions and loops). | |
| execution_scope = enclosing_scope; | |
| } | |
| for (size_t i = 0; i < statements_.size() && !err->has_error(); i++) { | |
| // Check for trying to execute things with no side effects in a block. | |
| // | |
| // A BlockNode here means that somebody has a free-floating { }. | |
| // Technically this can have side effects since it could generated targets, | |
| // but we don't want to allow this since it creates ambiguity when | |
| // immediately following a function call that takes no block. By not | |
| // allowing free-floating blocks that aren't passed anywhere or assigned to | |
| // anything, this ambiguity is resolved. | |
| const ParseNode* cur = statements_[i].get(); | |
| if (cur->AsList() || cur->AsLiteral() || cur->AsUnaryOp() || | |
| cur->AsIdentifier() || cur->AsBlock()) { | |
| *err = cur->MakeErrorDescribing( | |
| "This statement has no effect.", | |
| "Either delete it or do something with the result."); | |
| return Value(); | |
| } | |
| cur->Execute(execution_scope, err); | |
| } | |
| if (result_mode_ == RETURNS_SCOPE) { | |
| // Clear the reference to the containing scope. This will be passed in | |
| // a value whose lifetime will not be related to the enclosing_scope passed | |
| // to this function. | |
| nested_scope->DetachFromContaining(); | |
| return Value(this, std::move(nested_scope)); | |
| } | |
| return Value(); | |
| } | |
| LocationRange BlockNode::GetRange() const { | |
| if (begin_token_.type() != Token::INVALID && | |
| end_->value().type() != Token::INVALID) { | |
| return begin_token_.range().Union(end_->value().range()); | |
| } else if (!statements_.empty()) { | |
| return statements_[0]->GetRange().Union( | |
| statements_[statements_.size() - 1]->GetRange()); | |
| } | |
| return LocationRange(); | |
| } | |
| Err BlockNode::MakeErrorDescribing(const std::string& msg, | |
| const std::string& help) const { | |
| return Err(GetRange(), msg, help); | |
| } | |
| base::Value BlockNode::GetJSONNode() const { | |
| base::Value dict(CreateJSONNode("BLOCK")); | |
| base::Value statements(base::Value::Type::LIST); | |
| for (const auto& statement : statements_) | |
| statements.GetList().push_back(statement->GetJSONNode()); | |
| if (end_ && end_->comments()) | |
| statements.GetList().push_back(end_->GetJSONNode()); | |
| dict.SetKey("child", std::move(statements)); | |
| return dict; | |
| } | |
| // ConditionNode -------------------------------------------------------------- | |
| ConditionNode::ConditionNode() = default; | |
| ConditionNode::~ConditionNode() = default; | |
| const ConditionNode* ConditionNode::AsConditionNode() const { | |
| return this; | |
| } | |
| Value ConditionNode::Execute(Scope* scope, Err* err) const { | |
| Value condition_result = condition_->Execute(scope, err); | |
| if (err->has_error()) | |
| return Value(); | |
| if (condition_result.type() != Value::BOOLEAN) { | |
| *err = condition_->MakeErrorDescribing( | |
| "Condition does not evaluate to a boolean value.", | |
| std::string("This is a value of type \"") + | |
| Value::DescribeType(condition_result.type()) + "\" instead."); | |
| err->AppendRange(if_token_.range()); | |
| return Value(); | |
| } | |
| if (condition_result.boolean_value()) { | |
| if_true_->Execute(scope, err); | |
| } else if (if_false_) { | |
| // The else block is optional. | |
| if_false_->Execute(scope, err); | |
| } | |
| return Value(); | |
| } | |
| LocationRange ConditionNode::GetRange() const { | |
| if (if_false_) | |
| return if_token_.range().Union(if_false_->GetRange()); | |
| return if_token_.range().Union(if_true_->GetRange()); | |
| } | |
| Err ConditionNode::MakeErrorDescribing(const std::string& msg, | |
| const std::string& help) const { | |
| return Err(if_token_, msg, help); | |
| } | |
| base::Value ConditionNode::GetJSONNode() const { | |
| base::Value dict = CreateJSONNode("CONDITION"); | |
| base::Value child(base::Value::Type::LIST); | |
| child.GetList().push_back(condition_->GetJSONNode()); | |
| child.GetList().push_back(if_true_->GetJSONNode()); | |
| if (if_false_) { | |
| child.GetList().push_back(if_false_->GetJSONNode()); | |
| } | |
| dict.SetKey(kJsonNodeChild, std::move(child)); | |
| return std::move(dict); | |
| } | |
| // FunctionCallNode ----------------------------------------------------------- | |
| FunctionCallNode::FunctionCallNode() = default; | |
| FunctionCallNode::~FunctionCallNode() = default; | |
| const FunctionCallNode* FunctionCallNode::AsFunctionCall() const { | |
| return this; | |
| } | |
| Value FunctionCallNode::Execute(Scope* scope, Err* err) const { | |
| return functions::RunFunction(scope, this, args_.get(), block_.get(), err); | |
| } | |
| LocationRange FunctionCallNode::GetRange() const { | |
| if (function_.type() == Token::INVALID) | |
| return LocationRange(); // This will be null in some tests. | |
| if (block_) | |
| return function_.range().Union(block_->GetRange()); | |
| return function_.range().Union(args_->GetRange()); | |
| } | |
| Err FunctionCallNode::MakeErrorDescribing(const std::string& msg, | |
| const std::string& help) const { | |
| return Err(function_, msg, help); | |
| } | |
| base::Value FunctionCallNode::GetJSONNode() const { | |
| base::Value dict = CreateJSONNode("FUNCTION", function_.value()); | |
| base::Value child(base::Value::Type::LIST); | |
| child.GetList().push_back(args_->GetJSONNode()); | |
| if (block_) { | |
| child.GetList().push_back(block_->GetJSONNode()); | |
| } | |
| dict.SetKey(kJsonNodeChild, std::move(child)); | |
| return dict; | |
| } | |
| void FunctionCallNode::SetNewLocation(int line_number) { | |
| Location func_old_loc = function_.location(); | |
| Location func_new_loc = | |
| Location(func_old_loc.file(), line_number, func_old_loc.column_number(), | |
| func_old_loc.byte()); | |
| function_.set_location(func_new_loc); | |
| Location args_old_loc = args_->Begin().location(); | |
| Location args_new_loc = | |
| Location(args_old_loc.file(), line_number, args_old_loc.column_number(), | |
| args_old_loc.byte()); | |
| const_cast<Token&>(args_->Begin()).set_location(args_new_loc); | |
| const_cast<Token&>(args_->End()->value()).set_location(args_new_loc); | |
| } | |
| // IdentifierNode -------------------------------------------------------------- | |
| IdentifierNode::IdentifierNode() = default; | |
| IdentifierNode::IdentifierNode(const Token& token) : value_(token) {} | |
| IdentifierNode::~IdentifierNode() = default; | |
| const IdentifierNode* IdentifierNode::AsIdentifier() const { | |
| return this; | |
| } | |
| Value IdentifierNode::Execute(Scope* scope, Err* err) const { | |
| const Scope* found_in_scope = nullptr; | |
| const Value* value = | |
| scope->GetValueWithScope(value_.value(), true, &found_in_scope); | |
| Value result; | |
| if (!value) { | |
| *err = MakeErrorDescribing("Undefined identifier"); | |
| return result; | |
| } | |
| if (!EnsureNotReadingFromSameDeclareArgs(this, scope, found_in_scope, err)) | |
| return result; | |
| result = *value; | |
| result.set_origin(this); | |
| return result; | |
| } | |
| LocationRange IdentifierNode::GetRange() const { | |
| return value_.range(); | |
| } | |
| Err IdentifierNode::MakeErrorDescribing(const std::string& msg, | |
| const std::string& help) const { | |
| return Err(value_, msg, help); | |
| } | |
| base::Value IdentifierNode::GetJSONNode() const { | |
| return CreateJSONNode("IDENTIFIER", value_.value()); | |
| } | |
| void IdentifierNode::SetNewLocation(int line_number) { | |
| Location old = value_.location(); | |
| value_.set_location( | |
| Location(old.file(), line_number, old.column_number(), old.byte())); | |
| } | |
| // ListNode ------------------------------------------------------------------- | |
| ListNode::ListNode() : prefer_multiline_(false) {} | |
| ListNode::~ListNode() = default; | |
| const ListNode* ListNode::AsList() const { | |
| return this; | |
| } | |
| Value ListNode::Execute(Scope* scope, Err* err) const { | |
| Value result_value(this, Value::LIST); | |
| std::vector<Value>& results = result_value.list_value(); | |
| results.reserve(contents_.size()); | |
| for (const auto& cur : contents_) { | |
| if (cur->AsBlockComment()) | |
| continue; | |
| results.push_back(cur->Execute(scope, err)); | |
| if (err->has_error()) | |
| return Value(); | |
| if (results.back().type() == Value::NONE) { | |
| *err = cur->MakeErrorDescribing("This does not evaluate to a value.", | |
| "I can't do something with nothing."); | |
| return Value(); | |
| } | |
| } | |
| return result_value; | |
| } | |
| LocationRange ListNode::GetRange() const { | |
| return LocationRange(begin_token_.location(), end_->value().location()); | |
| } | |
| Err ListNode::MakeErrorDescribing(const std::string& msg, | |
| const std::string& help) const { | |
| return Err(begin_token_, msg, help); | |
| } | |
| base::Value ListNode::GetJSONNode() const { | |
| base::Value dict(CreateJSONNode("LIST")); | |
| base::Value child(base::Value::Type::LIST); | |
| for (const auto& cur : contents_) { | |
| child.GetList().push_back(cur->GetJSONNode()); | |
| } | |
| if (end_ && end_->comments()) { | |
| child.GetList().push_back(end_->GetJSONNode()); | |
| } | |
| dict.SetKey(kJsonNodeChild, std::move(child)); | |
| return dict; | |
| } | |
| template <typename Comparator> | |
| void ListNode::SortList(Comparator comparator) { | |
| // Partitions first on BlockCommentNodes and sorts each partition separately. | |
| for (auto sr : GetSortRanges()) { | |
| bool skip = false; | |
| for (size_t i = sr.begin; i != sr.end; ++i) { | |
| // Bails out if any of the nodes are unsupported. | |
| const ParseNode* node = contents_[i].get(); | |
| if (!node->AsLiteral() && !node->AsIdentifier() && !node->AsAccessor()) { | |
| skip = true; | |
| continue; | |
| } | |
| } | |
| if (skip) | |
| continue; | |
| // Save the original line number so that we can re-assign ranges. We assume | |
| // they're contiguous lines because GetSortRanges() does so above. We need | |
| // to re-assign these line numbers primiarily because `gn format` uses them | |
| // to determine whether two nodes were initially separated by a blank line | |
| // or not. | |
| int start_line = contents_[sr.begin]->GetRange().begin().line_number(); | |
| const ParseNode* original_first = contents_[sr.begin].get(); | |
| std::sort(contents_.begin() + sr.begin, contents_.begin() + sr.end, | |
| [&comparator](const std::unique_ptr<const ParseNode>& a, | |
| const std::unique_ptr<const ParseNode>& b) { | |
| return comparator(a.get(), b.get()); | |
| }); | |
| // If the beginning of the range had before comments, and the first node | |
| // moved during the sort, then move its comments to the new head of the | |
| // range. | |
| if (original_first->comments() && | |
| contents_[sr.begin].get() != original_first) { | |
| for (const auto& hc : original_first->comments()->before()) { | |
| const_cast<ParseNode*>(contents_[sr.begin].get()) | |
| ->comments_mutable() | |
| ->append_before(hc); | |
| } | |
| const_cast<ParseNode*>(original_first) | |
| ->comments_mutable() | |
| ->clear_before(); | |
| } | |
| const ParseNode* prev = nullptr; | |
| for (size_t i = sr.begin; i != sr.end; ++i) { | |
| const ParseNode* node = contents_[i].get(); | |
| DCHECK(node->AsLiteral() || node->AsIdentifier() || node->AsAccessor()); | |
| int line_number = | |
| prev ? prev->GetRange().end().line_number() + 1 : start_line; | |
| if (node->AsLiteral()) { | |
| const_cast<LiteralNode*>(node->AsLiteral()) | |
| ->SetNewLocation(line_number); | |
| } else if (node->AsIdentifier()) { | |
| const_cast<IdentifierNode*>(node->AsIdentifier()) | |
| ->SetNewLocation(line_number); | |
| } else if (node->AsAccessor()) { | |
| const_cast<AccessorNode*>(node->AsAccessor()) | |
| ->SetNewLocation(line_number); | |
| } | |
| prev = node; | |
| } | |
| } | |
| } | |
| void ListNode::SortAsStringsList() { | |
| // Sorts alphabetically. | |
| SortList([](const ParseNode* a, const ParseNode* b) { | |
| base::StringPiece astr = GetStringRepresentation(a); | |
| base::StringPiece bstr = GetStringRepresentation(b); | |
| return astr < bstr; | |
| }); | |
| } | |
| void ListNode::SortAsDepsList() { | |
| // Sorts first relative targets, then absolute, each group is sorted | |
| // alphabetically. | |
| SortList([](const ParseNode* a, const ParseNode* b) { | |
| base::StringPiece astr = GetStringRepresentation(a); | |
| base::StringPiece bstr = GetStringRepresentation(b); | |
| return std::make_pair(GetDepsCategory(astr), SplitAtFirst(astr, ':')) < | |
| std::make_pair(GetDepsCategory(bstr), SplitAtFirst(bstr, ':')); | |
| }); | |
| } | |
| // Breaks the ParseNodes of |contents| up by ranges that should be separately | |
| // sorted. In particular, we break at a block comment, or an item that has an | |
| // attached "before" comment and is separated by a blank line from the item | |
| // before it. The assumption is that both of these indicate a separate 'section' | |
| // of a sources block across which items should not be inter-sorted. | |
| std::vector<ListNode::SortRange> ListNode::GetSortRanges() const { | |
| std::vector<SortRange> ranges; | |
| const ParseNode* prev = nullptr; | |
| size_t begin = 0; | |
| for (size_t i = begin; i < contents_.size(); prev = contents_[i++].get()) { | |
| if (IsSortRangeSeparator(contents_[i].get(), prev)) { | |
| if (i > begin) { | |
| ranges.push_back(SortRange(begin, i)); | |
| // If |i| is an item with an attached comment, then we start the next | |
| // range at that point, because we want to include it in the sort. | |
| // Otherwise, it's a block comment which we skip over entirely because | |
| // we don't want to move or include it in the sort. The two cases are: | |
| // | |
| // sources = [ | |
| // "a", | |
| // "b", | |
| // | |
| // # | |
| // # This is a block comment. | |
| // # | |
| // | |
| // "c", | |
| // "d", | |
| // ] | |
| // | |
| // which contains 5 elements, and for which the ranges would be { [0, | |
| // 2), [3, 5) } (notably excluding 2, the block comment), and: | |
| // | |
| // sources = [ | |
| // "a", | |
| // "b", | |
| // | |
| // # This is a header comment. | |
| // "c", | |
| // "d", | |
| // ] | |
| // | |
| // which contains 4 elements, index 2 containing an attached 'before' | |
| // comments, and the ranges should be { [0, 2), [2, 4) }. | |
| if (!contents_[i]->AsBlockComment()) | |
| begin = i; | |
| else | |
| begin = i + 1; | |
| } else { | |
| // If it was a one item range, just skip over it. | |
| begin = i + 1; | |
| } | |
| } | |
| } | |
| if (begin != contents_.size()) | |
| ranges.push_back(SortRange(begin, contents_.size())); | |
| return ranges; | |
| } | |
| // LiteralNode ----------------------------------------------------------------- | |
| LiteralNode::LiteralNode() = default; | |
| LiteralNode::LiteralNode(const Token& token) : value_(token) {} | |
| LiteralNode::~LiteralNode() = default; | |
| const LiteralNode* LiteralNode::AsLiteral() const { | |
| return this; | |
| } | |
| Value LiteralNode::Execute(Scope* scope, Err* err) const { | |
| switch (value_.type()) { | |
| case Token::TRUE_TOKEN: | |
| return Value(this, true); | |
| case Token::FALSE_TOKEN: | |
| return Value(this, false); | |
| case Token::INTEGER: { | |
| base::StringPiece s = value_.value(); | |
| if ((s.starts_with("0") && s.size() > 1) || s.starts_with("-0")) { | |
| if (s == "-0") | |
| *err = MakeErrorDescribing("Negative zero doesn't make sense"); | |
| else | |
| *err = MakeErrorDescribing("Leading zeros not allowed"); | |
| return Value(); | |
| } | |
| int64_t result_int; | |
| if (!base::StringToInt64(s, &result_int)) { | |
| *err = MakeErrorDescribing("This does not look like an integer"); | |
| return Value(); | |
| } | |
| return Value(this, result_int); | |
| } | |
| case Token::STRING: { | |
| Value v(this, Value::STRING); | |
| ExpandStringLiteral(scope, value_, &v, err); | |
| return v; | |
| } | |
| default: | |
| NOTREACHED(); | |
| return Value(); | |
| } | |
| } | |
| LocationRange LiteralNode::GetRange() const { | |
| return value_.range(); | |
| } | |
| Err LiteralNode::MakeErrorDescribing(const std::string& msg, | |
| const std::string& help) const { | |
| return Err(value_, msg, help); | |
| } | |
| base::Value LiteralNode::GetJSONNode() const { | |
| return CreateJSONNode("LITERAL", value_.value()); | |
| } | |
| void LiteralNode::SetNewLocation(int line_number) { | |
| Location old = value_.location(); | |
| value_.set_location( | |
| Location(old.file(), line_number, old.column_number(), old.byte())); | |
| } | |
| // UnaryOpNode ---------------------------------------------------------------- | |
| UnaryOpNode::UnaryOpNode() = default; | |
| UnaryOpNode::~UnaryOpNode() = default; | |
| const UnaryOpNode* UnaryOpNode::AsUnaryOp() const { | |
| return this; | |
| } | |
| Value UnaryOpNode::Execute(Scope* scope, Err* err) const { | |
| Value operand_value = operand_->Execute(scope, err); | |
| if (err->has_error()) | |
| return Value(); | |
| return ExecuteUnaryOperator(scope, this, operand_value, err); | |
| } | |
| LocationRange UnaryOpNode::GetRange() const { | |
| return op_.range().Union(operand_->GetRange()); | |
| } | |
| Err UnaryOpNode::MakeErrorDescribing(const std::string& msg, | |
| const std::string& help) const { | |
| return Err(op_, msg, help); | |
| } | |
| base::Value UnaryOpNode::GetJSONNode() const { | |
| base::Value dict = CreateJSONNode("UNARY", op_.value()); | |
| base::Value child(base::Value::Type::LIST); | |
| child.GetList().push_back(operand_->GetJSONNode()); | |
| dict.SetKey(kJsonNodeChild, std::move(child)); | |
| return dict; | |
| } | |
| // BlockCommentNode ------------------------------------------------------------ | |
| BlockCommentNode::BlockCommentNode() = default; | |
| BlockCommentNode::~BlockCommentNode() = default; | |
| const BlockCommentNode* BlockCommentNode::AsBlockComment() const { | |
| return this; | |
| } | |
| Value BlockCommentNode::Execute(Scope* scope, Err* err) const { | |
| return Value(); | |
| } | |
| LocationRange BlockCommentNode::GetRange() const { | |
| return comment_.range(); | |
| } | |
| Err BlockCommentNode::MakeErrorDescribing(const std::string& msg, | |
| const std::string& help) const { | |
| return Err(comment_, msg, help); | |
| } | |
| base::Value BlockCommentNode::GetJSONNode() const { | |
| std::string escaped; | |
| base::EscapeJSONString(comment_.value().as_string(), false, &escaped); | |
| return CreateJSONNode("BLOCK_COMMENT", escaped); | |
| } | |
| // EndNode --------------------------------------------------------------------- | |
| EndNode::EndNode(const Token& token) : value_(token) {} | |
| EndNode::~EndNode() = default; | |
| const EndNode* EndNode::AsEnd() const { | |
| return this; | |
| } | |
| Value EndNode::Execute(Scope* scope, Err* err) const { | |
| return Value(); | |
| } | |
| LocationRange EndNode::GetRange() const { | |
| return value_.range(); | |
| } | |
| Err EndNode::MakeErrorDescribing(const std::string& msg, | |
| const std::string& help) const { | |
| return Err(value_, msg, help); | |
| } | |
| base::Value EndNode::GetJSONNode() const { | |
| return CreateJSONNode("END", value_.value()); | |
| } |