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 |