Chromium Code Reviews| 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.\n" | |
|
brettw
2014/09/25 22:11:32
Can you append here "(alpha, use at own risk)" or
scottmg
2014/09/25 23:03:11
Done.
| |
| 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 class Printer { | |
| 37 public: | |
| 38 Printer(); | |
| 39 ~Printer(); | |
| 40 | |
| 41 void Block(const ParseNode* file); | |
| 42 | |
| 43 std::string String() const { return output_; } | |
| 44 | |
| 45 private: | |
| 46 // Add to output. | |
| 47 void Print(base::StringPiece str); | |
| 48 | |
| 49 // Formatted output. | |
| 50 void Printf(const char* fmt, ...); | |
| 51 | |
| 52 void TrimAndPrintToken(const Token& token); | |
| 53 | |
| 54 // End the current line, flushing end of line comments. | |
| 55 void Newline(); | |
| 56 | |
| 57 // Remove trailing spaces from the current line. | |
| 58 void Trim(); | |
| 59 | |
| 60 // Get the 0-based x position on the current line. | |
| 61 int CurrentColumn(); | |
| 62 | |
| 63 enum ExprStyle { | |
| 64 kExprStyleRegular, | |
| 65 kExprStyleComment, | |
| 66 }; | |
| 67 // Print the expression to the output buffer. Returns the type of element | |
| 68 // added to the output. | |
| 69 ExprStyle Expr(const ParseNode* root); | |
| 70 | |
| 71 // Format a list of values using the given style. | |
|
brettw
2014/09/25 22:11:32
These enums should go at the top of the "private"
scottmg
2014/09/25 23:03:11
Done.
| |
| 72 enum SequenceStyle { | |
| 73 kSequenceStyleFunctionCall, | |
| 74 kSequenceStyleList, | |
| 75 kSequenceStyleBlock, | |
| 76 }; | |
| 77 | |
| 78 template <class PARSENODE> // Just for const covariance. | |
| 79 void Sequence(SequenceStyle style, const std::vector<PARSENODE*>& list); | |
| 80 | |
| 81 enum { kIndentSize = 2 }; | |
|
brettw
2014/09/25 22:11:32
Can this be a static const int instead so it's typ
scottmg
2014/09/25 23:03:11
Done.
| |
| 82 | |
| 83 std::string output_; // Output buffer. | |
| 84 std::vector<Token> comments_; // Pending end-of-line comments. | |
| 85 int margin_; // Left margin (number of spaces). | |
| 86 | |
| 87 DISALLOW_COPY_AND_ASSIGN(Printer); | |
| 88 }; | |
| 89 | |
| 90 Printer::Printer() : margin_(0) { | |
| 91 output_.reserve(100 << 10); | |
| 92 } | |
| 93 | |
| 94 Printer::~Printer() { | |
| 95 } | |
| 96 | |
| 97 void Printer::Print(base::StringPiece str) { | |
| 98 str.AppendToString(&output_); | |
| 99 } | |
| 100 | |
| 101 void Printer::Printf(const char* fmt, ...) { | |
| 102 va_list ap; | |
| 103 va_start(ap, fmt); | |
| 104 char buf[256]; // Intended for single line output, so shouldn't be a factor. | |
| 105 vsnprintf(buf, sizeof(buf), fmt, ap); | |
| 106 va_end(ap); | |
| 107 output_ += buf; | |
|
brettw
2014/09/25 22:11:32
Can this all be replaced with:
va_list ap;
va_
scottmg
2014/09/25 23:03:11
Good point! I had other more complicated format st
| |
| 108 } | |
| 109 | |
| 110 void Printer::TrimAndPrintToken(const Token& token) { | |
| 111 std::string trimmed; | |
| 112 TrimWhitespaceASCII(token.value().as_string(), base::TRIM_ALL, &trimmed); | |
| 113 Print(trimmed); | |
| 114 } | |
| 115 | |
| 116 void Printer::Newline() { | |
| 117 if (!comments_.empty()) { | |
| 118 Print(" "); | |
| 119 int i = 0; | |
| 120 for (const auto& c : comments_) { | |
| 121 if (i > 0) { | |
| 122 Trim(); | |
| 123 Printf("\n%*s", margin_, ""); | |
| 124 } | |
| 125 TrimAndPrintToken(c); | |
| 126 } | |
| 127 comments_.clear(); | |
| 128 } | |
| 129 Trim(); | |
| 130 Printf("\n%*s", margin_, ""); | |
| 131 } | |
| 132 | |
| 133 void Printer::Trim() { | |
| 134 size_t n = output_.size(); | |
| 135 while (n > 0 && output_[n - 1] == ' ') | |
| 136 --n; | |
| 137 output_.resize(n); | |
| 138 } | |
| 139 | |
| 140 int Printer::CurrentColumn() { | |
| 141 int n = 0; | |
| 142 while (n < static_cast<int>(output_.size()) && | |
| 143 output_[output_.size() - 1 - n] != '\n') { | |
| 144 ++n; | |
| 145 } | |
| 146 return n; | |
| 147 } | |
| 148 | |
| 149 void Printer::Block(const ParseNode* root) { | |
| 150 const BlockNode* block = root->AsBlock(); | |
| 151 | |
| 152 if (block->comments()) { | |
| 153 for (const auto& c : block->comments()->before()) { | |
| 154 TrimAndPrintToken(c); | |
| 155 Newline(); | |
| 156 } | |
| 157 } | |
| 158 | |
| 159 size_t i = 0; | |
| 160 for (const auto& stmt : block->statements()) { | |
| 161 Expr(stmt); | |
| 162 Newline(); | |
| 163 if (stmt->comments()) { | |
|
brettw
2014/09/25 22:11:32
What about "before" statement comments?
scottmg
2014/09/25 23:03:11
Hmm, the asymmetry is kind of confusing. The befor
brettw
2014/09/25 23:06:58
Can you explain this in a comment?
scottmg
2014/09/25 23:13:12
Done.
| |
| 164 for (const auto& c : stmt->comments()->after()) { | |
| 165 TrimAndPrintToken(c); | |
| 166 Newline(); | |
| 167 } | |
| 168 } | |
| 169 if (i < block->statements().size() - 1) | |
| 170 Newline(); | |
| 171 ++i; | |
| 172 } | |
| 173 | |
| 174 if (block->comments()) { | |
| 175 for (const auto& c : block->comments()->after()) { | |
| 176 TrimAndPrintToken(c); | |
| 177 Newline(); | |
| 178 } | |
| 179 } | |
| 180 } | |
| 181 | |
| 182 Printer::ExprStyle Printer::Expr(const ParseNode* root) { | |
| 183 ExprStyle result = kExprStyleRegular; | |
| 184 if (root->comments()) { | |
| 185 if (!root->comments()->before().empty()) { | |
| 186 Trim(); | |
| 187 // If there's already other text on the line, start a new line. | |
| 188 if (CurrentColumn() > 0) | |
| 189 Print("\n"); | |
| 190 // We're printing a line comment, so we need to be at the current margin. | |
| 191 Printf("%*s", margin_, ""); | |
| 192 for (const auto& c : root->comments()->before()) { | |
| 193 TrimAndPrintToken(c); | |
| 194 Newline(); | |
| 195 } | |
| 196 } | |
| 197 } | |
| 198 | |
| 199 if (const AccessorNode* accessor = root->AsAccessor()) { | |
| 200 Print("TODO(scottmg): AccessorNode"); | |
| 201 } else if (const BinaryOpNode* binop = root->AsBinaryOp()) { | |
| 202 // TODO(scottmg): Lots to do here for complex if expressions: reflowing, | |
| 203 // parenthesizing, etc. | |
| 204 Expr(binop->left()); | |
| 205 Print(" "); | |
| 206 Print(binop->op().value()); | |
| 207 Print(" "); | |
| 208 Expr(binop->right()); | |
| 209 } else if (const BlockNode* block = root->AsBlock()) { | |
| 210 Sequence(kSequenceStyleBlock, block->statements()); | |
| 211 } else if (const ConditionNode* condition = root->AsConditionNode()) { | |
| 212 Print("if ("); | |
| 213 Expr(condition->condition()); | |
| 214 Print(") {"); | |
| 215 margin_ += kIndentSize; | |
| 216 Newline(); | |
| 217 Block(condition->if_true()); | |
| 218 margin_ -= kIndentSize; | |
| 219 Trim(); | |
| 220 Printf("%*s}", margin_, ""); | |
| 221 if (condition->if_false()) { | |
| 222 Print(" else "); | |
| 223 // If it's a block it's a bare 'else', otherwise it's an 'else if'. See | |
| 224 // ConditionNode::Execute. | |
| 225 bool is_else_if = condition->if_false()->AsBlock() == NULL; | |
| 226 if (is_else_if) { | |
| 227 Expr(condition->if_false()); | |
| 228 } else { | |
| 229 Print("{"); | |
| 230 margin_ += kIndentSize; | |
| 231 Newline(); | |
| 232 Block(condition->if_false()); | |
| 233 margin_ -= kIndentSize; | |
| 234 Trim(); | |
| 235 Printf("%*s}", margin_, ""); | |
| 236 } | |
| 237 } | |
| 238 } else if (const FunctionCallNode* func_call = root->AsFunctionCall()) { | |
| 239 Print(func_call->function().value()); | |
| 240 Sequence(kSequenceStyleFunctionCall, func_call->args()->contents()); | |
| 241 Print(" {"); | |
| 242 margin_ += kIndentSize; | |
| 243 Newline(); | |
| 244 Block(func_call->block()); | |
| 245 margin_ -= kIndentSize; | |
| 246 Trim(); | |
| 247 Printf("%*s}", margin_, ""); | |
| 248 } else if (const IdentifierNode* identifier = root->AsIdentifier()) { | |
| 249 Print(identifier->value().value()); | |
| 250 } else if (const ListNode* list = root->AsList()) { | |
| 251 Sequence(kSequenceStyleList, list->contents()); | |
| 252 } else if (const LiteralNode* literal = root->AsLiteral()) { | |
| 253 // TODO(scottmg): Quoting? | |
| 254 Print(literal->value().value()); | |
| 255 } else if (const UnaryOpNode* unaryop = root->AsUnaryOp()) { | |
| 256 Print(unaryop->op().value()); | |
| 257 Expr(unaryop->operand()); | |
| 258 } else if (const BlockCommentNode* block_comment = root->AsBlockComment()) { | |
| 259 Print(block_comment->comment().value()); | |
| 260 result = kExprStyleComment; | |
| 261 } else { | |
| 262 CHECK(false) << "Unhandled case in Expr."; | |
| 263 } | |
| 264 | |
| 265 // Defer any end of line comment until we reach the newline. | |
| 266 if (root->comments() && !root->comments()->suffix().empty()) { | |
| 267 std::copy(root->comments()->suffix().begin(), | |
| 268 root->comments()->suffix().end(), | |
| 269 std::back_inserter(comments_)); | |
| 270 } | |
| 271 | |
| 272 return result; | |
| 273 } | |
| 274 | |
| 275 template <class PARSENODE> | |
| 276 void Printer::Sequence(SequenceStyle style, | |
| 277 const std::vector<PARSENODE*>& list) { | |
| 278 bool force_multiline = false; | |
| 279 if (style == kSequenceStyleFunctionCall) | |
| 280 Print("("); | |
| 281 else if (style == kSequenceStyleList) | |
| 282 Print("["); | |
| 283 | |
| 284 if (style == kSequenceStyleBlock) | |
| 285 force_multiline = true; | |
| 286 | |
| 287 // If there's before line comments, make sure we have a place to put them. | |
| 288 for (const auto& i : list) { | |
| 289 if (i->comments() && !i->comments()->before().empty()) | |
| 290 force_multiline = true; | |
| 291 } | |
| 292 | |
| 293 if (list.size() == 0 && !force_multiline) { | |
| 294 // No elements, and not forcing newlines, print nothing. | |
| 295 } else if (list.size() == 1 && !force_multiline) { | |
| 296 if (style != kSequenceStyleFunctionCall) | |
| 297 Print(" "); | |
| 298 Expr(list[0]); | |
| 299 if (style != kSequenceStyleFunctionCall) | |
| 300 Print(" "); | |
| 301 } else { | |
| 302 margin_ += kIndentSize; | |
| 303 size_t i = 0; | |
| 304 for (const auto& x : list) { | |
| 305 Newline(); | |
| 306 ExprStyle expr_style = Expr(x); | |
| 307 if (i < list.size() - 1 || style == kSequenceStyleList) { | |
| 308 if (expr_style == kExprStyleRegular) | |
| 309 Print(","); | |
| 310 else | |
| 311 Newline(); | |
| 312 } | |
| 313 ++i; | |
| 314 } | |
| 315 | |
| 316 margin_ -= kIndentSize; | |
| 317 Newline(); | |
| 318 } | |
| 319 | |
| 320 if (style == kSequenceStyleFunctionCall) | |
| 321 Print(")"); | |
| 322 else if (style == kSequenceStyleList) | |
| 323 Print("]"); | |
| 324 } | |
| 325 | |
| 326 } // namespace | |
| 327 | |
| 328 bool FormatFileToString(const std::string& input_filename, | |
| 329 std::string* output, bool dump_tree) { | |
|
brettw
2014/09/25 22:11:32
Out arg should be last.
scottmg
2014/09/25 23:03:11
Done.
| |
| 330 Setup setup; | |
| 331 Err err; | |
| 332 SourceFile input_file(input_filename); | |
| 333 const ParseNode* parse_node = | |
| 334 setup.scheduler().input_file_manager()->SyncLoadFile( | |
| 335 LocationRange(), &setup.build_settings(), input_file, &err); | |
| 336 if (err.has_error()) { | |
| 337 err.PrintToStdout(); | |
| 338 return false; | |
| 339 } | |
| 340 if (dump_tree) { | |
| 341 std::ostringstream os; | |
| 342 parse_node->Print(os, 0); | |
| 343 printf("----------------------\n"); | |
| 344 printf("-- PARSE TREE --------\n"); | |
| 345 printf("----------------------\n"); | |
| 346 printf("%s", os.str().c_str()); | |
| 347 printf("----------------------\n"); | |
| 348 } | |
| 349 Printer pr; | |
| 350 pr.Block(parse_node); | |
| 351 *output = pr.String(); | |
| 352 return true; | |
| 353 } | |
| 354 | |
| 355 int RunFormat(const std::vector<std::string>& args) { | |
| 356 // TODO(scottmg): Eventually, this should be a list/spec of files, and they | |
| 357 // should all be done in parallel and in-place. For now, we don't want to | |
| 358 // overwrite good data with mistakenly reformatted stuff, so we just simply | |
| 359 // print the formatted output to stdout. | |
| 360 if (args.size() != 1) { | |
| 361 Err(Location(), "Expecting exactly one argument, see `gn help format`.\n") | |
| 362 .PrintToStdout(); | |
| 363 return 1; | |
| 364 } | |
| 365 | |
| 366 bool dump_tree = | |
| 367 base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchDumpTree); | |
| 368 | |
| 369 std::string input_name = args[0]; | |
| 370 if (input_name[0] != '/') { | |
| 371 std::replace(input_name.begin(), input_name.end(), '\\', '/'); | |
| 372 input_name = "//" + input_name; | |
| 373 } | |
| 374 std::string output_string; | |
| 375 if (FormatFileToString(input_name, &output_string, dump_tree)) { | |
| 376 printf("%s", output_string.c_str()); | |
| 377 } | |
| 378 | |
| 379 return 0; | |
| 380 } | |
| 381 | |
| 382 } // namespace commands | |
| OLD | NEW |