// 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 <sstream> #include "base/command_line.h" #include "tools/gn/commands.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 kFormat[] = "format"; const char kFormat_HelpShort[] = "format: Format .gn file."; const char kFormat_Help[] = "gn format: Format .gn file. (ALPHA, WILL CURRENTLY DESTROY DATA!)\n" "\n" " gn format //some/BUILD.gn\n" " gn format some\\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"; namespace { const int kIndentSize = 2; 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 { kSequenceStyleFunctionCall, kSequenceStyleList, kSequenceStyleBlock, }; enum ExprStyle { kExprStyleRegular, kExprStyleComment, }; // 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(); // Get the 0-based x position on the current line. int CurrentColumn(); // Print the expression to the output buffer. Returns the type of element // added to the output. ExprStyle Expr(const ParseNode* root); template <class PARSENODE> // Just for const covariance. void Sequence(SequenceStyle style, const std::vector<PARSENODE*>& list); std::string output_; // Output buffer. std::vector<Token> comments_; // Pending end-of-line comments. int margin_; // Left margin (number of spaces). DISALLOW_COPY_AND_ASSIGN(Printer); }; Printer::Printer() : margin_(0) { output_.reserve(100 << 10); } 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; for (const auto& c : comments_) { if (i > 0) { Trim(); Print("\n"); PrintMargin(); } TrimAndPrintToken(c); } comments_.clear(); } Trim(); Print("\n"); PrintMargin(); } void Printer::Trim() { size_t n = output_.size(); while (n > 0 && output_[n - 1] == ' ') --n; output_.resize(n); } int Printer::CurrentColumn() { int n = 0; while (n < static_cast<int>(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); 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) Newline(); ++i; } if (block->comments()) { for (const auto& c : block->comments()->after()) { TrimAndPrintToken(c); Newline(); } } } Printer::ExprStyle Printer::Expr(const ParseNode* root) { 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(); } } } if (root->AsAccessor()) { Print("TODO(scottmg): AccessorNode"); } else if (const BinaryOpNode* binop = root->AsBinaryOp()) { // TODO(scottmg): Lots to do here for complex if expressions: reflowing, // parenthesizing, etc. Expr(binop->left()); Print(" "); Print(binop->op().value()); Print(" "); Expr(binop->right()); } else if (const BlockNode* block = root->AsBlock()) { Sequence(kSequenceStyleBlock, block->statements()); } else if (const ConditionNode* condition = root->AsConditionNode()) { Print("if ("); Expr(condition->condition()); Print(") {"); margin_ += kIndentSize; Newline(); Block(condition->if_true()); margin_ -= kIndentSize; Trim(); PrintMargin(); Print("}"); 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()); } else { Print("{"); margin_ += kIndentSize; Newline(); Block(condition->if_false()); margin_ -= kIndentSize; Trim(); PrintMargin(); Print("}"); } } } else if (const FunctionCallNode* func_call = root->AsFunctionCall()) { Print(func_call->function().value()); Sequence(kSequenceStyleFunctionCall, func_call->args()->contents()); Print(" {"); margin_ += kIndentSize; Newline(); Block(func_call->block()); margin_ -= kIndentSize; Trim(); PrintMargin(); Print("}"); } else if (const IdentifierNode* identifier = root->AsIdentifier()) { Print(identifier->value().value()); } else if (const ListNode* list = root->AsList()) { Sequence(kSequenceStyleList, list->contents()); } 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()); } else if (const BlockCommentNode* block_comment = root->AsBlockComment()) { Print(block_comment->comment().value()); result = kExprStyleComment; } else { CHECK(false) << "Unhandled case in Expr."; } // 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 <class PARSENODE> void Printer::Sequence(SequenceStyle style, const std::vector<PARSENODE*>& list) { bool force_multiline = false; if (style == kSequenceStyleFunctionCall) Print("("); else if (style == kSequenceStyleList) Print("["); if (style == kSequenceStyleBlock) 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) { if (style != kSequenceStyleFunctionCall) Print(" "); Expr(list[0]); CHECK(list[0]->comments()->after().empty()); if (style != kSequenceStyleFunctionCall) Print(" "); } else { margin_ += kIndentSize; size_t i = 0; for (const auto& x : list) { Newline(); ExprStyle expr_style = Expr(x); CHECK(x->comments()->after().empty()); if (i < list.size() - 1 || style == kSequenceStyleList) { if (expr_style == kExprStyleRegular) Print(","); else Newline(); } ++i; } margin_ -= kIndentSize; Newline(); } if (style == kSequenceStyleFunctionCall) Print(")"); else if (style == kSequenceStyleList) Print("]"); } } // namespace bool FormatFileToString(const std::string& input_filename, bool dump_tree, std::string* output) { Setup setup; Err err; SourceFile input_file(input_filename); const ParseNode* parse_node = setup.scheduler().input_file_manager()->SyncLoadFile( LocationRange(), &setup.build_settings(), input_file, &err); if (err.has_error()) { err.PrintToStdout(); return false; } if (dump_tree) { std::ostringstream os; parse_node->Print(os, 0); printf("----------------------\n"); printf("-- PARSE TREE --------\n"); printf("----------------------\n"); printf("%s", os.str().c_str()); printf("----------------------\n"); } Printer pr; pr.Block(parse_node); *output = pr.String(); return true; } int RunFormat(const std::vector<std::string>& args) { // TODO(scottmg): Eventually, this should be a list/spec of files, and they // should all be done in parallel and in-place. For now, we don't want to // overwrite good data with mistakenly reformatted stuff, so we just simply // print the formatted output to stdout. if (args.size() != 1) { Err(Location(), "Expecting exactly one argument, see `gn help format`.\n") .PrintToStdout(); return 1; } bool dump_tree = base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchDumpTree); std::string input_name = args[0]; if (input_name[0] != '/') { std::replace(input_name.begin(), input_name.end(), '\\', '/'); input_name = "//" + input_name; } std::string output_string; if (FormatFileToString(input_name, dump_tree, &output_string)) { printf("%s", output_string.c_str()); } return 0; } } // namespace commands