OLD | NEW |
(Empty) | |
| 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 |
OLD | NEW |