Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(345)

Unified Diff: tools/gn/command_format.cc

Issue 591373002: gn: start of format command (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@gn-more-comment-stuff
Patch Set: ignore blockcomment in list eval Created 6 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: tools/gn/command_format.cc
diff --git a/tools/gn/command_format.cc b/tools/gn/command_format.cc
new file mode 100644
index 0000000000000000000000000000000000000000..117700284b5977c6932c04ac124b894d32d2f424
--- /dev/null
+++ b/tools/gn/command_format.cc
@@ -0,0 +1,382 @@
+// 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.\n"
brettw 2014/09/25 22:11:32 Can you append here "(alpha, use at own risk)" or
scottmg 2014/09/25 23:03:11 Done.
+ "\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 {
+
+class Printer {
+ public:
+ Printer();
+ ~Printer();
+
+ void Block(const ParseNode* file);
+
+ std::string String() const { return output_; }
+
+ private:
+ // Add to output.
+ void Print(base::StringPiece str);
+
+ // Formatted output.
+ void Printf(const char* fmt, ...);
+
+ 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();
+
+ enum ExprStyle {
+ kExprStyleRegular,
+ kExprStyleComment,
+ };
+ // Print the expression to the output buffer. Returns the type of element
+ // added to the output.
+ ExprStyle Expr(const ParseNode* root);
+
+ // Format a list of values using the given style.
brettw 2014/09/25 22:11:32 These enums should go at the top of the "private"
scottmg 2014/09/25 23:03:11 Done.
+ enum SequenceStyle {
+ kSequenceStyleFunctionCall,
+ kSequenceStyleList,
+ kSequenceStyleBlock,
+ };
+
+ template <class PARSENODE> // Just for const covariance.
+ void Sequence(SequenceStyle style, const std::vector<PARSENODE*>& list);
+
+ enum { kIndentSize = 2 };
brettw 2014/09/25 22:11:32 Can this be a static const int instead so it's typ
scottmg 2014/09/25 23:03:11 Done.
+
+ 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::Printf(const char* fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ char buf[256]; // Intended for single line output, so shouldn't be a factor.
+ vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+ output_ += buf;
brettw 2014/09/25 22:11:32 Can this all be replaced with: va_list ap; va_
scottmg 2014/09/25 23:03:11 Good point! I had other more complicated format st
+}
+
+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();
+ Printf("\n%*s", margin_, "");
+ }
+ TrimAndPrintToken(c);
+ }
+ comments_.clear();
+ }
+ Trim();
+ Printf("\n%*s", margin_, "");
+}
+
+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()) {
brettw 2014/09/25 22:11:32 What about "before" statement comments?
scottmg 2014/09/25 23:03:11 Hmm, the asymmetry is kind of confusing. The befor
brettw 2014/09/25 23:06:58 Can you explain this in a comment?
scottmg 2014/09/25 23:13:12 Done.
+ 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.
+ Printf("%*s", margin_, "");
+ for (const auto& c : root->comments()->before()) {
+ TrimAndPrintToken(c);
+ Newline();
+ }
+ }
+ }
+
+ if (const AccessorNode* accessor = 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();
+ Printf("%*s}", margin_, "");
+ 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();
+ Printf("%*s}", margin_, "");
+ }
+ }
+ } 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();
+ Printf("%*s}", margin_, "");
+ } 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]);
+ if (style != kSequenceStyleFunctionCall)
+ Print(" ");
+ } else {
+ margin_ += kIndentSize;
+ size_t i = 0;
+ for (const auto& x : list) {
+ Newline();
+ ExprStyle expr_style = Expr(x);
+ 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,
+ std::string* output, bool dump_tree) {
brettw 2014/09/25 22:11:32 Out arg should be last.
scottmg 2014/09/25 23:03:11 Done.
+ 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, &output_string, dump_tree)) {
+ printf("%s", output_string.c_str());
+ }
+
+ return 0;
+}
+
+} // namespace commands

Powered by Google App Engine
This is Rietveld 408576698