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