blob: 890cf2c0ed7bc529f2986b39b633ee19633cbdf9 [file] [log] [blame]
// 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 "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 <class PARSENODE> // Just for const covariance.
void Sequence(SequenceStyle style,
const std::vector<PARSENODE*>& list,
const ParseNode* end);
void FunctionCall(const FunctionCallNode* func_call);
std::string output_; // Output buffer.
std::vector<Token> comments_; // Pending end-of-line comments.
int margin_; // Left margin (number of spaces).
// Gives the precedence for operators in a BinaryOpNode.
std::map<base::StringPiece, Precedence> 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) {
LocationRange a_range = a->GetRange();
LocationRange b_range = b->GetRange();
// If they're already separated by 1 or more lines, then we want to keep a
// blank line.
return b_range.begin().line_number() > a_range.end().line_number() + 1;
}
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, 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<std::string> lines;
base::SplitStringDontTrim(sub.String(), '\n', &lines);
result.multiline = lines.size() > 1;
result.first_length = static_cast<int>(lines[0].size());
for (const auto& line : lines) {
result.longest_length =
std::max(result.longest_length, static_cast<int>(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<int>(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 <class PARSENODE>
void Printer::Sequence(SequenceStyle style,
const std::vector<PARSENODE*>& 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.size() >= 2)
Newline();
for (const auto& c : end->comments()->before()) {
Newline();
TrimAndPrintToken(c);
}
}
margin_ -= kIndentSize;
Newline();
// Defer any end of line comment until we reach the newline.
if (end->comments() && !end->comments()->suffix().empty()) {
std::copy(end->comments()->suffix().begin(),
end->comments()->suffix().end(),
std::back_inserter(comments_));
}
}
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<const ParseNode*>& 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<int> 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<int>(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();
}
}
}
// Defer any end of line comment until we reach the newline.
if (end->comments() && !end->comments()->suffix().empty()) {
std::copy(end->comments()->suffix().begin(),
end->comments()->suffix().end(),
std::back_inserter(comments_));
}
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<int>(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<Token> tokens = Tokenizer::Tokenize(&file, &err);
if (err.has_error()) {
err.PrintToStdout();
return false;
}
// Parse.
scoped_ptr<ParseNode> 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<std::string>& 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<int>(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