// Copyright 2014 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 #include "base/command_line.h" #include "base/files/file_util.h" #include "base/strings/string_split.h" #include "tools/gn/commands.h" #include "tools/gn/filesystem_utils.h" #include "tools/gn/input_file.h" #include "tools/gn/parser.h" #include "tools/gn/scheduler.h" #include "tools/gn/setup.h" #include "tools/gn/source_file.h" #include "tools/gn/tokenizer.h" namespace commands { const char kSwitchDumpTree[] = "dump-tree"; const char kSwitchInPlace[] = "in-place"; const char kSwitchStdin[] = "stdin"; const char kFormat[] = "format"; const char kFormat_HelpShort[] = "format: Format .gn file. (ALPHA, WILL DESTROY DATA!)"; const char kFormat_Help[] = "gn format [--dump-tree] [--in-place] [--stdin] BUILD.gn\n" "\n" " Formats .gn file to a standard format. THIS IS NOT FULLY IMPLEMENTED\n" " YET! IT WILL EAT YOUR BEAUTIFUL .GN FILES. AND YOUR LAUNDRY.\n" " At a minimum, make sure everything is `git commit`d so you can\n" " `git checkout -f` to recover.\n" "\n" "Arguments\n" " --dump-tree\n" " For debugging only, dumps the parse tree.\n" "\n" " --in-place\n" " Instead writing the formatted file to stdout, replace the input\n" " with the formatted output.\n" "\n" " --stdin\n" " Read input from stdin (and write to stdout). Not compatible with\n" " --in-place of course.\n" "\n" "Examples\n" " gn format //some/BUILD.gn\n" " gn format some\\BUILD.gn\n" " gn format /abspath/some/BUILD.gn\n" " gn format --stdin\n"; namespace { const int kIndentSize = 2; const int kMaximumWidth = 80; enum Precedence { kPrecedenceLowest, kPrecedenceAssign, kPrecedenceOr, kPrecedenceAnd, kPrecedenceCompare, kPrecedenceAdd, kPrecedenceSuffix, kPrecedenceUnary, }; class Printer { public: Printer(); ~Printer(); void Block(const ParseNode* file); std::string String() const { return output_; } private: // Format a list of values using the given style. enum SequenceStyle { kSequenceStyleList, kSequenceStyleBlock, kSequenceStyleBracedBlock, }; enum ExprStyle { kExprStyleRegular, kExprStyleComment, }; struct Metrics { Metrics() : first_length(-1), longest_length(-1), multiline(false) {} int first_length; int longest_length; bool multiline; }; // Add to output. void Print(base::StringPiece str); // Add the current margin (as spaces) to the output. void PrintMargin(); void TrimAndPrintToken(const Token& token); // End the current line, flushing end of line comments. void Newline(); // Remove trailing spaces from the current line. void Trim(); // Whether there's a blank separator line at the current position. bool HaveBlankLine(); bool IsAssignment(const ParseNode* node); // Heuristics to decide if there should be a blank line added between two // items. For various "small" items, it doesn't look nice if there's too much // vertical whitespace added. bool ShouldAddBlankLineInBetween(const ParseNode* a, const ParseNode* b); // Get the 0-based x position on the current line. int CurrentColumn(); // Adds an opening ( if prec is less than the outers (to maintain evalution // order for a subexpression). If an opening paren is emitted, *parenthesized // will be set so it can be closed at the end of the expression. void AddParen(int prec, int outer_prec, bool* parenthesized); // Print the expression to the output buffer. Returns the type of element // added to the output. The value of outer_prec gives the precedence of the // operator outside this Expr. If that operator binds tighter than root's, // Expr must introduce parentheses. ExprStyle Expr(const ParseNode* root, int outer_prec); // Use a sub-Printer recursively to figure out the size that an expression // would be before actually adding it to the output. Metrics GetLengthOfExpr(const ParseNode* expr, int outer_prec); // Format a list of values using the given style. // |end| holds any trailing comments to be printed just before the closing // bracket. template // Just for const covariance. void Sequence(SequenceStyle style, const std::vector& list, const ParseNode* end); void FunctionCall(const FunctionCallNode* func_call); std::string output_; // Output buffer. std::vector comments_; // Pending end-of-line comments. int margin_; // Left margin (number of spaces). // Gives the precedence for operators in a BinaryOpNode. std::map precedence_; DISALLOW_COPY_AND_ASSIGN(Printer); }; Printer::Printer() : margin_(0) { output_.reserve(100 << 10); precedence_["="] = kPrecedenceAssign; precedence_["+="] = kPrecedenceAssign; precedence_["-="] = kPrecedenceAssign; precedence_["||"] = kPrecedenceOr; precedence_["&&"] = kPrecedenceAnd; precedence_["<"] = kPrecedenceCompare; precedence_[">"] = kPrecedenceCompare; precedence_["=="] = kPrecedenceCompare; precedence_["!="] = kPrecedenceCompare; precedence_["<="] = kPrecedenceCompare; precedence_[">="] = kPrecedenceCompare; precedence_["+"] = kPrecedenceAdd; precedence_["-"] = kPrecedenceAdd; precedence_["!"] = kPrecedenceUnary; } Printer::~Printer() { } void Printer::Print(base::StringPiece str) { str.AppendToString(&output_); } void Printer::PrintMargin() { output_ += std::string(margin_, ' '); } void Printer::TrimAndPrintToken(const Token& token) { std::string trimmed; TrimWhitespaceASCII(token.value().as_string(), base::TRIM_ALL, &trimmed); Print(trimmed); } void Printer::Newline() { if (!comments_.empty()) { Print(" "); int i = 0; // Save the margin, and temporarily set it to where the first comment // starts so that multiple suffix comments are vertically aligned. This // will need to be fancier once we enforce 80 col. int old_margin = margin_; for (const auto& c : comments_) { if (i == 0) margin_ = CurrentColumn(); else { Trim(); Print("\n"); PrintMargin(); } TrimAndPrintToken(c); ++i; } margin_ = old_margin; comments_.clear(); } Trim(); Print("\n"); PrintMargin(); } void Printer::Trim() { size_t n = output_.size(); while (n > 0 && output_[n - 1] == ' ') --n; output_.resize(n); } bool Printer::HaveBlankLine() { size_t n = output_.size(); while (n > 0 && output_[n - 1] == ' ') --n; return n > 2 && output_[n - 1] == '\n' && output_[n - 2] == '\n'; } bool Printer::IsAssignment(const ParseNode* node) { return node->AsBinaryOp() && (node->AsBinaryOp()->op().value() == "=" || node->AsBinaryOp()->op().value() == "+=" || node->AsBinaryOp()->op().value() == "-="); } bool Printer::ShouldAddBlankLineInBetween(const ParseNode* a, const ParseNode* b) { if ((IsAssignment(a) || a->AsFunctionCall()) && (!a->comments() || a->comments()->after().size() == 0) && (IsAssignment(b) || b->AsFunctionCall()) && (!b->comments() || b->comments()->before().size() == 0)) { Metrics first = GetLengthOfExpr(a, kPrecedenceLowest); Metrics second = GetLengthOfExpr(b, kPrecedenceLowest); if (!first.multiline || !second.multiline) return false; } return true; } int Printer::CurrentColumn() { int n = 0; while (n < static_cast(output_.size()) && output_[output_.size() - 1 - n] != '\n') { ++n; } return n; } void Printer::Block(const ParseNode* root) { const BlockNode* block = root->AsBlock(); if (block->comments()) { for (const auto& c : block->comments()->before()) { TrimAndPrintToken(c); Newline(); } } size_t i = 0; for (const auto& stmt : block->statements()) { Expr(stmt, kPrecedenceLowest); Newline(); if (stmt->comments()) { // Why are before() not printed here too? before() are handled inside // Expr(), as are suffix() which are queued to the next Newline(). // However, because it's a general expression handler, it doesn't insert // the newline itself, which only happens between block statements. So, // the after are handled explicitly here. for (const auto& c : stmt->comments()->after()) { TrimAndPrintToken(c); Newline(); } } if (i < block->statements().size() - 1 && (ShouldAddBlankLineInBetween(block->statements()[i], block->statements()[i + 1]))) { Newline(); } ++i; } if (block->comments()) { for (const auto& c : block->comments()->after()) { TrimAndPrintToken(c); Newline(); } } } Printer::Metrics Printer::GetLengthOfExpr(const ParseNode* expr, int outer_prec) { Metrics result; Printer sub; sub.Expr(expr, outer_prec); std::vector lines; base::SplitStringDontTrim(sub.String(), '\n', &lines); result.multiline = lines.size() > 1; result.first_length = static_cast(lines[0].size()); for (const auto& line : lines) { result.longest_length = std::max(result.longest_length, static_cast(line.size())); } return result; } void Printer::AddParen(int prec, int outer_prec, bool* parenthesized) { if (prec < outer_prec) { Print("("); *parenthesized = true; } } Printer::ExprStyle Printer::Expr(const ParseNode* root, int outer_prec) { ExprStyle result = kExprStyleRegular; if (root->comments()) { if (!root->comments()->before().empty()) { Trim(); // If there's already other text on the line, start a new line. if (CurrentColumn() > 0) Print("\n"); // We're printing a line comment, so we need to be at the current margin. PrintMargin(); for (const auto& c : root->comments()->before()) { TrimAndPrintToken(c); Newline(); } } } bool parenthesized = false; if (const AccessorNode* accessor = root->AsAccessor()) { AddParen(kPrecedenceSuffix, outer_prec, &parenthesized); Print(accessor->base().value()); if (accessor->member()) { Print("."); Expr(accessor->member(), kPrecedenceLowest); } else { CHECK(accessor->index()); Print("["); Expr(accessor->index(), kPrecedenceLowest); Print("]"); } } else if (const BinaryOpNode* binop = root->AsBinaryOp()) { CHECK(precedence_.find(binop->op().value()) != precedence_.end()); Precedence prec = precedence_[binop->op().value()]; AddParen(prec, outer_prec, &parenthesized); Metrics right = GetLengthOfExpr(binop->right(), prec + 1); int op_length = static_cast(binop->op().value().size()) + 2; Expr(binop->left(), prec); if (CurrentColumn() + op_length + right.first_length <= kMaximumWidth) { // If it just fits normally, put it here. Print(" "); Print(binop->op().value()); Print(" "); Expr(binop->right(), prec + 1); } else { // Otherwise, put first argument and op, and indent next. Print(" "); Print(binop->op().value()); int old_margin = margin_; margin_ += kIndentSize * 2; Newline(); Expr(binop->right(), prec + 1); margin_ = old_margin; } } else if (const BlockNode* block = root->AsBlock()) { Sequence(kSequenceStyleBracedBlock, block->statements(), block->End()); } else if (const ConditionNode* condition = root->AsConditionNode()) { Print("if ("); Expr(condition->condition(), kPrecedenceLowest); Print(") "); Sequence(kSequenceStyleBracedBlock, condition->if_true()->statements(), condition->if_true()->End()); if (condition->if_false()) { Print(" else "); // If it's a block it's a bare 'else', otherwise it's an 'else if'. See // ConditionNode::Execute. bool is_else_if = condition->if_false()->AsBlock() == NULL; if (is_else_if) { Expr(condition->if_false(), kPrecedenceLowest); } else { Sequence(kSequenceStyleBracedBlock, condition->if_false()->AsBlock()->statements(), condition->if_false()->AsBlock()->End()); } } } else if (const FunctionCallNode* func_call = root->AsFunctionCall()) { FunctionCall(func_call); } else if (const IdentifierNode* identifier = root->AsIdentifier()) { Print(identifier->value().value()); } else if (const ListNode* list = root->AsList()) { Sequence(kSequenceStyleList, list->contents(), list->End()); } else if (const LiteralNode* literal = root->AsLiteral()) { // TODO(scottmg): Quoting? Print(literal->value().value()); } else if (const UnaryOpNode* unaryop = root->AsUnaryOp()) { Print(unaryop->op().value()); Expr(unaryop->operand(), kPrecedenceUnary); } else if (const BlockCommentNode* block_comment = root->AsBlockComment()) { Print(block_comment->comment().value()); result = kExprStyleComment; } else if (const EndNode* end = root->AsEnd()) { Print(end->value().value()); } else { CHECK(false) << "Unhandled case in Expr."; } if (parenthesized) Print(")"); // Defer any end of line comment until we reach the newline. if (root->comments() && !root->comments()->suffix().empty()) { std::copy(root->comments()->suffix().begin(), root->comments()->suffix().end(), std::back_inserter(comments_)); } return result; } template void Printer::Sequence(SequenceStyle style, const std::vector& list, const ParseNode* end) { bool force_multiline = false; if (style == kSequenceStyleList) Print("["); else if (style == kSequenceStyleBracedBlock) Print("{"); if (style == kSequenceStyleBlock || style == kSequenceStyleBracedBlock) force_multiline = true; if (end && end->comments() && !end->comments()->before().empty()) force_multiline = true; // If there's before line comments, make sure we have a place to put them. for (const auto& i : list) { if (i->comments() && !i->comments()->before().empty()) force_multiline = true; } if (list.size() == 0 && !force_multiline) { // No elements, and not forcing newlines, print nothing. } else if (list.size() == 1 && !force_multiline) { Print(" "); Expr(list[0], kPrecedenceLowest); CHECK(!list[0]->comments() || list[0]->comments()->after().empty()); Print(" "); } else { margin_ += kIndentSize; size_t i = 0; for (const auto& x : list) { Newline(); // If: // - we're going to output some comments, and; // - we haven't just started this multiline list, and; // - there isn't already a blank line here; // Then: insert one. if (i != 0 && x->comments() && !x->comments()->before().empty() && !HaveBlankLine()) { Newline(); } ExprStyle expr_style = Expr(x, kPrecedenceLowest); CHECK(!x->comments() || x->comments()->after().empty()); if (i < list.size() - 1 || style == kSequenceStyleList) { if (style == kSequenceStyleList && expr_style == kExprStyleRegular) { Print(","); } else { if (i < list.size() - 1 && ShouldAddBlankLineInBetween(list[i], list[i + 1])) Newline(); } } ++i; } // Trailing comments. if (end->comments()) { if (!list.empty()) Newline(); for (const auto& c : end->comments()->before()) { Newline(); TrimAndPrintToken(c); } } margin_ -= kIndentSize; Newline(); } if (style == kSequenceStyleList) Print("]"); else if (style == kSequenceStyleBracedBlock) Print("}"); } void Printer::FunctionCall(const FunctionCallNode* func_call) { Print(func_call->function().value()); Print("("); int old_margin = margin_; bool have_block = func_call->block() != nullptr; bool force_multiline = false; const std::vector& list = func_call->args()->contents(); const ParseNode* end = func_call->args()->End(); if (end && end->comments() && !end->comments()->before().empty()) force_multiline = true; // If there's before line comments, make sure we have a place to put them. for (const auto& i : list) { if (i->comments() && !i->comments()->before().empty()) force_multiline = true; } // Calculate the length of the items for function calls so we can decide to // compress them in various nicer ways. std::vector natural_lengths; bool fits_on_current_line = true; int max_item_width = 0; int total_length = 0; natural_lengths.reserve(list.size()); std::string terminator = ")"; if (have_block) terminator += " {"; for (size_t i = 0; i < list.size(); ++i) { Metrics sub = GetLengthOfExpr(list[i], kPrecedenceLowest); if (sub.multiline) fits_on_current_line = false; natural_lengths.push_back(sub.longest_length); total_length += sub.longest_length; if (i < list.size() - 1) { total_length += static_cast(strlen(", ")); } } fits_on_current_line = fits_on_current_line && CurrentColumn() + total_length + terminator.size() <= kMaximumWidth; if (natural_lengths.size() > 0) { max_item_width = *std::max_element(natural_lengths.begin(), natural_lengths.end()); } if (list.size() == 0 && !force_multiline) { // No elements, and not forcing newlines, print nothing. } else if (list.size() == 1 && !force_multiline && fits_on_current_line) { Expr(list[0], kPrecedenceLowest); CHECK(!list[0]->comments() || list[0]->comments()->after().empty()); } else { // Function calls get to be single line even with multiple arguments, if // they fit inside the maximum width. if (!force_multiline && fits_on_current_line) { for (size_t i = 0; i < list.size(); ++i) { Expr(list[i], kPrecedenceLowest); if (i < list.size() - 1) Print(", "); } } else { bool should_break_to_next_line = true; int indent = kIndentSize * 2; if (CurrentColumn() + max_item_width + terminator.size() <= kMaximumWidth || CurrentColumn() < margin_ + indent) { should_break_to_next_line = false; margin_ = CurrentColumn(); } else { margin_ += indent; } size_t i = 0; for (const auto& x : list) { // Function calls where all the arguments would fit at the current // position should do that instead of going back to margin+4. if (i > 0 || should_break_to_next_line) Newline(); ExprStyle expr_style = Expr(x, kPrecedenceLowest); CHECK(!x->comments() || x->comments()->after().empty()); if (i < list.size() - 1) { if (expr_style == kExprStyleRegular) { Print(","); } else { Newline(); } } ++i; } // Trailing comments. if (end->comments()) { if (!list.empty()) Newline(); for (const auto& c : end->comments()->before()) { Newline(); TrimAndPrintToken(c); } if (!end->comments()->before().empty()) Newline(); } } } Print(")"); margin_ = old_margin; if (have_block) { Print(" "); Sequence(kSequenceStyleBracedBlock, func_call->block()->statements(), func_call->block()->End()); } } void DoFormat(const ParseNode* root, bool dump_tree, std::string* output) { if (dump_tree) { std::ostringstream os; root->Print(os, 0); printf("----------------------\n"); printf("-- PARSE TREE --------\n"); printf("----------------------\n"); printf("%s", os.str().c_str()); printf("----------------------\n"); } Printer pr; pr.Block(root); *output = pr.String(); } std::string ReadStdin() { static const int kBufferSize = 256; char buffer[kBufferSize]; std::string result; while (true) { char* input = NULL; input = fgets(buffer, kBufferSize, stdin); if (input == NULL && feof(stdin)) return result; int length = static_cast(strlen(buffer)); if (length == 0) return result; else result += std::string(buffer, length); } } } // namespace bool FormatFileToString(Setup* setup, const SourceFile& file, bool dump_tree, std::string* output) { Err err; const ParseNode* parse_node = setup->scheduler().input_file_manager()->SyncLoadFile( LocationRange(), &setup->build_settings(), file, &err); if (err.has_error()) { err.PrintToStdout(); return false; } DoFormat(parse_node, dump_tree, output); return true; } bool FormatStringToString(const std::string& input, bool dump_tree, std::string* output) { SourceFile source_file; InputFile file(source_file); file.SetContents(input); Err err; // Tokenize. std::vector tokens = Tokenizer::Tokenize(&file, &err); if (err.has_error()) { err.PrintToStdout(); return false; } // Parse. scoped_ptr parse_node = Parser::Parse(tokens, &err); if (err.has_error()) { err.PrintToStdout(); return false; } DoFormat(parse_node.get(), dump_tree, output); return true; } int RunFormat(const std::vector& args) { bool dump_tree = base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchDumpTree); bool from_stdin = base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchStdin); if (from_stdin) { if (args.size() != 0) { Err(Location(), "Expecting no arguments when reading from stdin.\n") .PrintToStdout(); return 1; } std::string input = ReadStdin(); std::string output; if (!FormatStringToString(input, dump_tree, &output)) return 1; printf("%s", output.c_str()); return 0; } // TODO(scottmg): Eventually, this should be a list/spec of files, and they // should all be done in parallel. if (args.size() != 1) { Err(Location(), "Expecting exactly one argument, see `gn help format`.\n") .PrintToStdout(); return 1; } Setup setup; SourceDir source_dir = SourceDirForCurrentDirectory(setup.build_settings().root_path()); SourceFile file = source_dir.ResolveRelativeFile(args[0]); std::string output_string; if (FormatFileToString(&setup, file, dump_tree, &output_string)) { bool in_place = base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchInPlace); if (in_place) { base::FilePath to_write = setup.build_settings().GetFullPath(file); if (base::WriteFile(to_write, output_string.data(), static_cast(output_string.size())) == -1) { Err(Location(), std::string("Failed to write formatted output back to \"") + to_write.AsUTF8Unsafe() + std::string("\".")).PrintToStdout(); return 1; } printf("Wrote formatted to '%s'.\n", to_write.AsUTF8Unsafe().c_str()); } else { printf("%s", output_string.c_str()); } } return 0; } } // namespace commands