Home | History | Annotate | Download | only in gn
      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 
     16 namespace commands {
     17 
     18 const char kSwitchDumpTree[] = "dump-tree";
     19 
     20 const char kFormat[] = "format";
     21 const char kFormat_HelpShort[] =
     22     "format: Format .gn file.";
     23 const 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 
     34 namespace {
     35 
     36 const int kIndentSize = 2;
     37 
     38 class 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 
     91 Printer::Printer() : margin_(0) {
     92   output_.reserve(100 << 10);
     93 }
     94 
     95 Printer::~Printer() {
     96 }
     97 
     98 void Printer::Print(base::StringPiece str) {
     99   str.AppendToString(&output_);
    100 }
    101 
    102 void Printer::PrintMargin() {
    103   output_ += std::string(margin_, ' ');
    104 }
    105 
    106 void Printer::TrimAndPrintToken(const Token& token) {
    107   std::string trimmed;
    108   TrimWhitespaceASCII(token.value().as_string(), base::TRIM_ALL, &trimmed);
    109   Print(trimmed);
    110 }
    111 
    112 void 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 
    131 void Printer::Trim() {
    132   size_t n = output_.size();
    133   while (n > 0 && output_[n - 1] == ' ')
    134     --n;
    135   output_.resize(n);
    136 }
    137 
    138 int 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 
    147 void 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 
    185 Printer::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 
    281 template <class PARSENODE>
    282 void 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 
    336 bool 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 
    364 int 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