Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(274)

Side by Side Diff: tools/gn/command_format2.cc

Issue 750373002: XXX WIP gn format: Dijkstra-style line break Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: . Created 6 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | tools/gn/gn.gyp » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 "base/files/file_util.h"
9 #include "base/strings/string_split.h"
10 #include "tools/gn/commands.h"
11 #include "tools/gn/filesystem_utils.h"
12 #include "tools/gn/input_file.h"
13 #include "tools/gn/parser.h"
14 #include "tools/gn/scheduler.h"
15 #include "tools/gn/setup.h"
16 #include "tools/gn/source_file.h"
17 #include "tools/gn/tokenizer.h"
18
19 namespace commands {
20
21 const char kSwitchDumpTree[] = "dump-tree";
22 const char kSwitchInPlace[] = "in-place";
23 const char kSwitchStdin[] = "stdin";
24
25 const char kFormat[] = "format";
26 const char kFormat_HelpShort[] =
27 "format: Format .gn file. (ALPHA, WILL DESTROY DATA!)";
28 const char kFormat_Help[] =
29 "gn format [--dump-tree] [--in-place] [--stdin] BUILD.gn\n"
30 "\n"
31 " Formats .gn file to a standard format. THIS IS NOT FULLY IMPLEMENTED\n"
32 " YET! IT WILL EAT YOUR BEAUTIFUL .GN FILES. AND YOUR LAUNDRY.\n"
33 " At a minimum, make sure everything is `git commit`d so you can\n"
34 " `git checkout -f` to recover.\n"
35 "\n"
36 "Arguments\n"
37 " --dump-tree\n"
38 " For debugging only, dumps the parse tree.\n"
39 "\n"
40 " --in-place\n"
41 " Instead writing the formatted file to stdout, replace the input\n"
42 " with the formatted output.\n"
43 "\n"
44 " --stdin\n"
45 " Read input from stdin (and write to stdout).\n"
46 "\n"
47 "Examples\n"
48 " gn format //some/BUILD.gn\n"
49 " gn format some\\BUILD.gn\n"
50 " gn format /abspath/some/BUILD.gn\n"
51 " gn format --stdin\n";
52
53 const int kIndentSize = 2;
54 //const int kMaximumWidth = 80;
55
56 struct AnnotatedLine;
57 struct AnnotatedLineNode;
58
59 enum FormatDecision {
60 FD_UNFORMATTED,
61 FD_CONTINUE,
62 FD_BREAK
63 };
64
65 // -----------------------------------------------------------------------------
66 // A wrapper around Token storing information about whitespace characters
67 // preceding it.
68 struct FormatToken {
69 FormatToken()
70 : newlines_before(0),
71 decision(FD_UNFORMATTED),
72 total_length(0),
73 previous(nullptr),
74 next(nullptr) {}
75
76 // The token.
77 Token tok;
78
79 // Number of newlines immediately before the token. This can be used to
80 // determine what the user wrote in the original code to e.g. leave an empty
81 // line between two function definitions.
82 int newlines_before;
83
84 /// Stores the formatting decision for the token once it was made.
85 FormatDecision decision;
86
87 // The total length of the annotated line up to and including this token.
88 int total_length;
89
90 FormatToken* previous;
91 FormatToken* next;
92
93 std::vector<AnnotatedLine*> children;
94
95
96 FormatToken* GetPreviousNonComment() const {
97 FormatToken* tok = previous;
98 /* TODO
99 while (tok && tok->Is(COMMENT))
100 tok = tok->previous;
101 */
102 return tok;
103 }
104 };
105
106 // -----------------------------------------------------------------------------
107 struct ParenState {
108 ParenState(int indent, int indent_level, int last_space, bool no_line_break)
109 : indent(indent),
110 indent_level(indent_level),
111 last_space(last_space),
112 no_line_break(no_line_break) {}
113
114 // The position to which a specific parenthesis level needs to be indented.
115 int indent;
116
117 // The number of indentation levels of the block.
118 int indent_level;
119
120 // The potision of the last space on each level.
121 // Used to break in the case of:
122 // Function(parameter, OtherCall(
123 // other_parameter))
124 int last_space;
125
126 // Break after the next comma.
127 bool break_before_parameter;
128
129 // Line breaking in this context would break a formatting rule.
130 bool no_line_break;
131
132 // If the last binary operator on this level was wrapped to th next line.
133 bool last_operator_wrapped;
134
135 bool operator<(const ParenState& rhs) const {
136 if (indent != rhs.indent)
137 return indent < rhs.indent;
138 if (indent_level != rhs.indent_level)
139 return indent_level < rhs.indent_level;
140 if (last_space != rhs.last_space)
141 return last_space < rhs.last_space;
142 if (break_before_parameter != rhs.break_before_parameter)
143 return break_before_parameter;
144 if (no_line_break != rhs.no_line_break)
145 return no_line_break;
146 if (last_operator_wrapped != rhs.last_operator_wrapped)
147 return last_operator_wrapped;
148 return false;
149 }
150 };
151
152 // -----------------------------------------------------------------------------
153 // The current state when indenting a chunk.
154 //
155 // As the indenting tries different combinations, this is copied by value.
156 struct LineState {
157 // The number of used columns in the current line.
158 int column;
159
160 // The token that needs to be formatted next.
161 FormatToken* next_token;
162
163 // A stack tracing the properties applying to parenthesis levels.
164 std::vector<ParenState> stack;
165
166 // The indent of the first token.
167 int first_indent;
168
169 const AnnotatedLine* line;
170
171 // To be able to use LineState in a map.
172 bool operator<(const LineState& rhs) const {
173 if (next_token != rhs.next_token)
174 return next_token < rhs.next_token;
175 if (column != rhs.column)
176 return column < rhs.column;
177 if (first_indent != rhs.first_indent)
178 return first_indent < rhs.first_indent;
179 return stack < rhs.stack;
180 }
181 };
182
183 // -----------------------------------------------------------------------------
184 class WhitespaceManager {
185 public:
186 void ReplaceWhitespace(FormatToken* tok,
187 int newlines,
188 int indent_level,
189 int spaces,
190 int start_of_token_column);
191 };
192
193 // -----------------------------------------------------------------------------
194 class ContinuationIndenter {
195 public:
196 ContinuationIndenter(WhitespaceManager* whitespaces)
197 : whitespaces_(whitespaces) {}
198
199 // Get the initial state (the state after place |line|s first token and
200 // |first_indent|).
201 LineState GetInitialState(int first_indent,
202 const AnnotatedLine* line,
203 bool dry_run);
204
205 // Appends the next token to state and updates information necessary for
206 // indentation.
207 //
208 // Puts the token on the current line if newline is false and adds a line
209 // break and necessary indentation otherwise.
210 int AddTokenToState(LineState* state,
211 bool newline,
212 int dry_run,
213 int extra_spaces);
214
215 private:
216 WhitespaceManager* whitespaces_;
217 };
218
219 int ContinuationIndenter::AddTokenToState(LineState* state,
220 bool newline,
221 int dry_run,
222 int extra_spaces) {
223 const FormatToken& current = *state.next_token;
224 }
225
226 // -----------------------------------------------------------------------------
227 // An annotated line is a sequence of Token that we would prefer to put on a
228 // single line if there were no column limit.
229 //
230 // The parse tree is flattened down AnnotatedLines and passed to
231 // AnnotatedLineFormatter.
232 struct AnnotatedLine {
233 AnnotatedLine() : level(0) {}
234
235 // The indent level of the line.
236 int level;
237
238 FormatToken* first;
239 FormatToken* last;
240
241 std::vector<AnnotatedLine*> children;
242
243 std::list<AnnotatedLineNode> tokens;
244 };
245
246 // -----------------------------------------------------------------------------
247 struct AnnotatedLineNode {
248 AnnotatedLineNode() : tok(nullptr) {}
249 AnnotatedLineNode(FormatToken* tok) : tok(tok) {}
250
251 FormatToken *tok;
252 std::vector<AnnotatedLine> children;
253 };
254
255 // -----------------------------------------------------------------------------
256 class AnnotatedLineFormatter {
257 public:
258 AnnotatedLineFormatter(ContinuationIndenter* indenter,
259 WhitespaceManager* whitespaces)
260 : indenter_(indenter) /*, whitespaces_(whitespaces)*/ {}
261
262 // Formats a line, and returns the penalty.
263 int FormatLine(const AnnotatedLine& line, int first_indent, bool dry_run);
264
265 int FormatLines(const std::vector<AnnotatedLine*>& lines,
266 bool dry_run,
267 int additional_indent,
268 bool fix_bad_indentation);
269
270 private:
271 struct CompareLineStatePointers {
272 bool operator()(LineState* a, LineState* b) const { return *a < *b; }
273 };
274
275 struct StateNode {
276 StateNode(const LineState& state, bool newline, StateNode* previous)
277 : state(state), newline(newline), previous(previous) {}
278 LineState state;
279 bool newline;
280 StateNode* previous;
281 };
282
283 // A pair of <penalty, count> that is used to prioritize the BFS.
284 //
285 // In the case of equal penalties, we want to prefer states that were inserted
286 // first. During state generation, we make sure that we insert states first
287 // that break the line as late as possible.
288 typedef std::pair<unsigned, unsigned> OrderedPenalty;
289
290 // An item in the BFS search queue. The StateNode's State has the given
291 // OrderedPenalty.
292 typedef std::pair<OrderedPenalty, StateNode*> QueueItem;
293
294 // The BFS queue type.
295 typedef std::priority_queue<QueueItem,
296 std::vector<QueueItem>,
297 std::greater<QueueItem>> QueueType;
298
299 // Analyze the entire solution space starting from |initial_state|.
300 //
301 // This implements Dijkstra's algorithm on the graph that has LineStates as
302 // nodes. It tries to find the lowest penalty path from |initial_state| to a
303 // state where all tokens are placed. Returns the penalty.
304 int AnalyzeSolutionSpace(LineState* initial_state, bool dry_run);
305
306 // Add the following state to the analysis queue.
307 //
308 // Assume the current state is previous_node and has been reached with the
309 // given penalty. Insert a newline if newline is true.
310 void AddNextStateToQueue(int penalty,
311 StateNode* previous_node,
312 bool newline,
313 int* count,
314 QueueType* queue);
315
316 // If the state's next token is a } closing a nested block, format the nested
317 // block before it.
318 bool FormatChildren(LineState* state,
319 bool newline,
320 bool dry_run,
321 int* penalty);
322
323 ContinuationIndenter* indenter_;
324 //WhitespaceManager* whitespaces_;
325 };
326
327 int AnnotatedLineFormatter::FormatLines(
328 const std::vector<AnnotatedLine*>& lines,
329 bool dry_run,
330 int additional_indent,
331 bool fix_bad_indentation) {
332 return 0;
333 }
334
335 int AnnotatedLineFormatter::FormatLine(const AnnotatedLine& line,
336 int first_indent,
337 bool dry_run) {
338 LineState state = indenter_->GetInitialState(first_indent, &line, dry_run);
339 return AnalyzeSolutionSpace(&state, dry_run);
340 }
341
342 int AnnotatedLineFormatter::AnalyzeSolutionSpace(LineState* initial_state,
343 bool dry_run) {
344 std::set<LineState*, CompareLineStatePointers> seen;
345
346 // Increasing count of StateNode items we have created. This is used to
347 // create a deterministic order independent of the container.
348 int count = 0;
349 QueueType queue;
350
351 // Insert start element into queue.
352 StateNode* node = new StateNode(*initial_state, false, nullptr);
353 queue.push(QueueItem(OrderedPenalty(0, count), node));
354 ++count;
355
356 int penalty = 0;
357
358 // While not empty, take first element and follow edges.
359 while (!queue.empty()) {
360 penalty = queue.top().first.first;
361 StateNode* node = queue.top().second;
362 if (!node->state.next_token) {
363 fprintf(stderr, "\n---\nPenalty for line: %d\n", penalty);
364 break;
365 }
366 queue.pop();
367
368 if (!seen.insert(&node->state).second) {
369 // State already examinied with lower penalty.
370 continue;
371 }
372
373 FormatDecision last_format = node->state.next_token->decision;
374 if (last_format == FD_UNFORMATTED || last_format == FD_CONTINUE)
375 AddNextStateToQueue(penalty, node, false, &count, &queue);
376 if (last_format == FD_UNFORMATTED || last_format == FD_BREAK)
377 AddNextStateToQueue(penalty, node, true, &count, &queue);
378 }
379
380 if (queue.empty()) {
381 fprintf(stderr, "Could not find a solution.\n");
382 return 0;
383 }
384
385 if (!dry_run) {
386 fprintf(stderr, "todo; ReconstructPath\n");
387 }
388
389 fprintf(stderr, "Total number of analyzed states: %d\n---\n", count);
390
391 return penalty;
392 }
393
394 void AnnotatedLineFormatter::AddNextStateToQueue(int penalty,
395 StateNode* previous_node,
396 bool newline,
397 int* count,
398 QueueType* queue) {
399 StateNode* node =
400 new StateNode(previous_node->state, newline, previous_node);
401 if (!FormatChildren(&node->state, newline, true, &penalty))
402 return;
403
404 penalty = indenter_->AddTokenToState(&node->state, newline, true, 0);
405 queue->push(QueueItem(OrderedPenalty(penalty, *count), node));
406 ++(*count);
407 }
408
409 bool AnnotatedLineFormatter::FormatChildren(LineState* state,
410 bool newline,
411 bool dry_run,
412 int* penalty) {
413 FormatToken* previous = state->next_token->previous;
414 const FormatToken* lbrace = state->next_token->GetPreviousNonComment();
415 if (!lbrace || previous->children.size() == 0 /* TODO: stuff */)
416 return true;
417
418 if (newline) {
419 int additional_indent =
420 state->first_indent - state->line->level * kIndentSize;
421 penalty +=
422 FormatLines(previous->children, dry_run, additional_indent, true);
423 return true;
424 }
425
426 // Can't merge multiple statements into a single line.
427 if (previous->children.size() > 1)
428 return false;
429
430 // Can't merge into one line if this line ends on a comment.
431 //if (previous.is(COMMENT))
432 //return false;
433
434 // TODO: more
435
436 penalty +=
437 FormatLine(*previous->children[0], state->column + 1, dry_run);
438 state->column += 1 + previous->children[0]->last->total_length;
439 return true;
440 }
441
442 // -----------------------------------------------------------------------------
443 void DoFormat(const ParseNode* root, bool dump_tree, std::string* output) {
444 *output = std::string();
445 }
446
447 std::string ReadStdin() {
448 static const int kBufferSize = 256;
449 char buffer[kBufferSize];
450 std::string result;
451 while (true) {
452 char* input = NULL;
453 input = fgets(buffer, kBufferSize, stdin);
454 if (input == NULL && feof(stdin))
455 return result;
456 int length = static_cast<int>(strlen(buffer));
457 if (length == 0)
458 return result;
459 else
460 result += std::string(buffer, length);
461 }
462 }
463
464
465
466
467 bool FormatFileToString(Setup* setup,
468 const SourceFile& file,
469 bool dump_tree,
470 std::string* output) {
471 Err err;
472 const ParseNode* parse_node =
473 setup->scheduler().input_file_manager()->SyncLoadFile(
474 LocationRange(), &setup->build_settings(), file, &err);
475 if (err.has_error()) {
476 err.PrintToStdout();
477 return false;
478 }
479 DoFormat(parse_node, dump_tree, output);
480 return true;
481 }
482
483 bool FormatStringToString(const std::string& input,
484 bool dump_tree,
485 std::string* output) {
486 SourceFile source_file;
487 InputFile file(source_file);
488 file.SetContents(input);
489 Err err;
490 // Tokenize.
491 std::vector<Token> tokens = Tokenizer::Tokenize(&file, &err);
492 if (err.has_error()) {
493 err.PrintToStdout();
494 return false;
495 }
496
497 // Parse.
498 scoped_ptr<ParseNode> parse_node = Parser::Parse(tokens, &err);
499 if (err.has_error()) {
500 err.PrintToStdout();
501 return false;
502 }
503
504 DoFormat(parse_node.get(), dump_tree, output);
505 return true;
506 }
507
508 int RunFormat(const std::vector<std::string>& args) {
509 bool dump_tree =
510 base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchDumpTree);
511
512 bool from_stdin =
513 base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchStdin);
514
515 if (from_stdin) {
516 if (args.size() != 0) {
517 Err(Location(), "Expecting no arguments when reading from stdin.\n")
518 .PrintToStdout();
519 return 1;
520 }
521 std::string input = ReadStdin();
522 std::string output;
523 if (!FormatStringToString(input, dump_tree, &output))
524 return 1;
525 printf("%s", output.c_str());
526 return 0;
527 }
528
529 // TODO(scottmg): Eventually, this should be a list/spec of files, and they
530 // should all be done in parallel.
531 if (args.size() != 1) {
532 Err(Location(), "Expecting exactly one argument, see `gn help format`.\n")
533 .PrintToStdout();
534 return 1;
535 }
536
537 Setup setup;
538 SourceDir source_dir =
539 SourceDirForCurrentDirectory(setup.build_settings().root_path());
540 SourceFile file = source_dir.ResolveRelativeFile(args[0],
541 setup.build_settings().root_path_utf8());
542
543 std::string output_string;
544 if (FormatFileToString(&setup, file, dump_tree, &output_string)) {
545 bool in_place =
546 base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchInPlace);
547 if (in_place) {
548 base::FilePath to_write = setup.build_settings().GetFullPath(file);
549 if (base::WriteFile(to_write,
550 output_string.data(),
551 static_cast<int>(output_string.size())) == -1) {
552 Err(Location(),
553 std::string("Failed to write formatted output back to \"") +
554 to_write.AsUTF8Unsafe() + std::string("\".")).PrintToStdout();
555 return 1;
556 }
557 printf("Wrote formatted to '%s'.\n", to_write.AsUTF8Unsafe().c_str());
558 } else {
559 printf("%s", output_string.c_str());
560 }
561 }
562
563 return 0;
564 }
565
566 } // namespace commands
OLDNEW
« no previous file with comments | « no previous file | tools/gn/gn.gyp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698