1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <sstream>
6
7#include "base/command_line.h"
8#include "tools/gn/commands.h"
9#include "tools/gn/input_file.h"
10#include "tools/gn/parser.h"
11#include "tools/gn/scheduler.h"
12#include "tools/gn/setup.h"
13#include "tools/gn/source_file.h"
14#include "tools/gn/tokenizer.h"
15
16namespace commands {
17
18const char kSwitchDumpTree[] = "dump-tree";
19
20const char kFormat[] = "format";
21const char kFormat_HelpShort[] =
22    "format: Format .gn file.";
23const char kFormat_Help[] =
24    "gn format: Format .gn file. (ALPHA, WILL CURRENTLY DESTROY DATA!)\n"
25    "\n"
26    "  gn format //some/BUILD.gn\n"
27    "  gn format some\\BUILD.gn\n"
28    "\n"
29    "  Formats .gn file to a standard format. THIS IS NOT FULLY IMPLEMENTED\n"
30    "  YET! IT WILL EAT YOUR BEAUTIFUL .GN FILES. AND YOUR LAUNDRY.\n"
31    "  At a minimum, make sure everything is `git commit`d so you can\n"
32    "  `git checkout -f` to recover.\n";
33
34namespace {
35
36const int kIndentSize = 2;
37
38class Printer {
39 public:
40  Printer();
41  ~Printer();
42
43  void Block(const ParseNode* file);
44
45  std::string String() const { return output_; }
46
47 private:
48  // Format a list of values using the given style.
49  enum SequenceStyle {
50    kSequenceStyleFunctionCall,
51    kSequenceStyleList,
52    kSequenceStyleBlock,
53  };
54
55  enum ExprStyle {
56    kExprStyleRegular,
57    kExprStyleComment,
58  };
59
60  // Add to output.
61  void Print(base::StringPiece str);
62
63  // Add the current margin (as spaces) to the output.
64  void PrintMargin();
65
66  void TrimAndPrintToken(const Token& token);
67
68  // End the current line, flushing end of line comments.
69  void Newline();
70
71  // Remove trailing spaces from the current line.
72  void Trim();
73
74  // Get the 0-based x position on the current line.
75  int CurrentColumn();
76
77  // Print the expression to the output buffer. Returns the type of element
78  // added to the output.
79  ExprStyle Expr(const ParseNode* root);
80
81  template <class PARSENODE>  // Just for const covariance.
82  void Sequence(SequenceStyle style, const std::vector<PARSENODE*>& list);
83
84  std::string output_;           // Output buffer.
85  std::vector<Token> comments_;  // Pending end-of-line comments.
86  int margin_;                   // Left margin (number of spaces).
87
88  DISALLOW_COPY_AND_ASSIGN(Printer);
89};
90
91Printer::Printer() : margin_(0) {
92  output_.reserve(100 << 10);
93}
94
95Printer::~Printer() {
96}
97
98void Printer::Print(base::StringPiece str) {
99  str.AppendToString(&output_);
100}
101
102void Printer::PrintMargin() {
103  output_ += std::string(margin_, ' ');
104}
105
106void Printer::TrimAndPrintToken(const Token& token) {
107  std::string trimmed;
108  TrimWhitespaceASCII(token.value().as_string(), base::TRIM_ALL, &trimmed);
109  Print(trimmed);
110}
111
112void Printer::Newline() {
113  if (!comments_.empty()) {
114    Print("  ");
115    int i = 0;
116    for (const auto& c : comments_) {
117      if (i > 0) {
118        Trim();
119        Print("\n");
120        PrintMargin();
121      }
122      TrimAndPrintToken(c);
123    }
124    comments_.clear();
125  }
126  Trim();
127  Print("\n");
128  PrintMargin();
129}
130
131void Printer::Trim() {
132  size_t n = output_.size();
133  while (n > 0 && output_[n - 1] == ' ')
134    --n;
135  output_.resize(n);
136}
137
138int Printer::CurrentColumn() {
139  int n = 0;
140  while (n < static_cast<int>(output_.size()) &&
141         output_[output_.size() - 1 - n] != '\n') {
142    ++n;
143  }
144  return n;
145}
146
147void Printer::Block(const ParseNode* root) {
148  const BlockNode* block = root->AsBlock();
149
150  if (block->comments()) {
151    for (const auto& c : block->comments()->before()) {
152      TrimAndPrintToken(c);
153      Newline();
154    }
155  }
156
157  size_t i = 0;
158  for (const auto& stmt : block->statements()) {
159    Expr(stmt);
160    Newline();
161    if (stmt->comments()) {
162      // Why are before() not printed here too? before() are handled inside
163      // Expr(), as are suffix() which are queued to the next Newline().
164      // However, because it's a general expression handler, it doesn't insert
165      // the newline itself, which only happens between block statements. So,
166      // the after are handled explicitly here.
167      for (const auto& c : stmt->comments()->after()) {
168        TrimAndPrintToken(c);
169        Newline();
170      }
171    }
172    if (i < block->statements().size() - 1)
173      Newline();
174    ++i;
175  }
176
177  if (block->comments()) {
178    for (const auto& c : block->comments()->after()) {
179      TrimAndPrintToken(c);
180      Newline();
181    }
182  }
183}
184
185Printer::ExprStyle Printer::Expr(const ParseNode* root) {
186  ExprStyle result = kExprStyleRegular;
187  if (root->comments()) {
188    if (!root->comments()->before().empty()) {
189      Trim();
190      // If there's already other text on the line, start a new line.
191      if (CurrentColumn() > 0)
192        Print("\n");
193      // We're printing a line comment, so we need to be at the current margin.
194      PrintMargin();
195      for (const auto& c : root->comments()->before()) {
196        TrimAndPrintToken(c);
197        Newline();
198      }
199    }
200  }
201
202  if (root->AsAccessor()) {
203    Print("TODO(scottmg): AccessorNode");
204  } else if (const BinaryOpNode* binop = root->AsBinaryOp()) {
205    // TODO(scottmg): Lots to do here for complex if expressions: reflowing,
206    // parenthesizing, etc.
207    Expr(binop->left());
208    Print(" ");
209    Print(binop->op().value());
210    Print(" ");
211    Expr(binop->right());
212  } else if (const BlockNode* block = root->AsBlock()) {
213    Sequence(kSequenceStyleBlock, block->statements());
214  } else if (const ConditionNode* condition = root->AsConditionNode()) {
215    Print("if (");
216    Expr(condition->condition());
217    Print(") {");
218    margin_ += kIndentSize;
219    Newline();
220    Block(condition->if_true());
221    margin_ -= kIndentSize;
222    Trim();
223    PrintMargin();
224    Print("}");
225    if (condition->if_false()) {
226      Print(" else ");
227      // If it's a block it's a bare 'else', otherwise it's an 'else if'. See
228      // ConditionNode::Execute.
229      bool is_else_if = condition->if_false()->AsBlock() == NULL;
230      if (is_else_if) {
231        Expr(condition->if_false());
232      } else {
233        Print("{");
234        margin_ += kIndentSize;
235        Newline();
236        Block(condition->if_false());
237        margin_ -= kIndentSize;
238        Trim();
239        PrintMargin();
240        Print("}");
241      }
242    }
243  } else if (const FunctionCallNode* func_call = root->AsFunctionCall()) {
244    Print(func_call->function().value());
245    Sequence(kSequenceStyleFunctionCall, func_call->args()->contents());
246    Print(" {");
247    margin_ += kIndentSize;
248    Newline();
249    Block(func_call->block());
250    margin_ -= kIndentSize;
251    Trim();
252    PrintMargin();
253    Print("}");
254  } else if (const IdentifierNode* identifier = root->AsIdentifier()) {
255    Print(identifier->value().value());
256  } else if (const ListNode* list = root->AsList()) {
257    Sequence(kSequenceStyleList, list->contents());
258  } else if (const LiteralNode* literal = root->AsLiteral()) {
259    // TODO(scottmg): Quoting?
260    Print(literal->value().value());
261  } else if (const UnaryOpNode* unaryop = root->AsUnaryOp()) {
262    Print(unaryop->op().value());
263    Expr(unaryop->operand());
264  } else if (const BlockCommentNode* block_comment = root->AsBlockComment()) {
265    Print(block_comment->comment().value());
266    result = kExprStyleComment;
267  } else {
268    CHECK(false) << "Unhandled case in Expr.";
269  }
270
271  // Defer any end of line comment until we reach the newline.
272  if (root->comments() && !root->comments()->suffix().empty()) {
273    std::copy(root->comments()->suffix().begin(),
274              root->comments()->suffix().end(),
275              std::back_inserter(comments_));
276  }
277
278  return result;
279}
280
281template <class PARSENODE>
282void Printer::Sequence(SequenceStyle style,
283                       const std::vector<PARSENODE*>& list) {
284  bool force_multiline = false;
285  if (style == kSequenceStyleFunctionCall)
286    Print("(");
287  else if (style == kSequenceStyleList)
288    Print("[");
289
290  if (style == kSequenceStyleBlock)
291    force_multiline = true;
292
293  // If there's before line comments, make sure we have a place to put them.
294  for (const auto& i : list) {
295    if (i->comments() && !i->comments()->before().empty())
296      force_multiline = true;
297  }
298
299  if (list.size() == 0 && !force_multiline) {
300    // No elements, and not forcing newlines, print nothing.
301  } else if (list.size() == 1 && !force_multiline) {
302    if (style != kSequenceStyleFunctionCall)
303      Print(" ");
304    Expr(list[0]);
305    CHECK(list[0]->comments()->after().empty());
306    if (style != kSequenceStyleFunctionCall)
307      Print(" ");
308  } else {
309    margin_ += kIndentSize;
310    size_t i = 0;
311    for (const auto& x : list) {
312      Newline();
313      ExprStyle expr_style = Expr(x);
314      CHECK(x->comments()->after().empty());
315      if (i < list.size() - 1 || style == kSequenceStyleList) {
316        if (expr_style == kExprStyleRegular)
317          Print(",");
318        else
319          Newline();
320      }
321      ++i;
322    }
323
324    margin_ -= kIndentSize;
325    Newline();
326  }
327
328  if (style == kSequenceStyleFunctionCall)
329    Print(")");
330  else if (style == kSequenceStyleList)
331    Print("]");
332}
333
334}  // namespace
335
336bool FormatFileToString(const std::string& input_filename,
337                        bool dump_tree,
338                        std::string* output) {
339  Setup setup;
340  Err err;
341  SourceFile input_file(input_filename);
342  const ParseNode* parse_node =
343      setup.scheduler().input_file_manager()->SyncLoadFile(
344          LocationRange(), &setup.build_settings(), input_file, &err);
345  if (err.has_error()) {
346    err.PrintToStdout();
347    return false;
348  }
349  if (dump_tree) {
350    std::ostringstream os;
351    parse_node->Print(os, 0);
352    printf("----------------------\n");
353    printf("-- PARSE TREE --------\n");
354    printf("----------------------\n");
355    printf("%s", os.str().c_str());
356    printf("----------------------\n");
357  }
358  Printer pr;
359  pr.Block(parse_node);
360  *output = pr.String();
361  return true;
362}
363
364int RunFormat(const std::vector<std::string>& args) {
365  // TODO(scottmg): Eventually, this should be a list/spec of files, and they
366  // should all be done in parallel and in-place. For now, we don't want to
367  // overwrite good data with mistakenly reformatted stuff, so we just simply
368  // print the formatted output to stdout.
369  if (args.size() != 1) {
370    Err(Location(), "Expecting exactly one argument, see `gn help format`.\n")
371        .PrintToStdout();
372    return 1;
373  }
374
375  bool dump_tree =
376      base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchDumpTree);
377
378  std::string input_name = args[0];
379  if (input_name[0] != '/') {
380    std::replace(input_name.begin(), input_name.end(), '\\', '/');
381    input_name = "//" + input_name;
382  }
383  std::string output_string;
384  if (FormatFileToString(input_name, dump_tree, &output_string)) {
385    printf("%s", output_string.c_str());
386  }
387
388  return 0;
389}
390
391}  // namespace commands
392