Chromium Code Reviews| 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 |