Index: tools/gn/command_format2.cc |
diff --git a/tools/gn/command_format2.cc b/tools/gn/command_format2.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..820294361850c8f72c65bb31f6f2fe295bef73a1 |
--- /dev/null |
+++ b/tools/gn/command_format2.cc |
@@ -0,0 +1,566 @@ |
+// Copyright 2014 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include <sstream> |
+ |
+#include "base/command_line.h" |
+#include "base/files/file_util.h" |
+#include "base/strings/string_split.h" |
+#include "tools/gn/commands.h" |
+#include "tools/gn/filesystem_utils.h" |
+#include "tools/gn/input_file.h" |
+#include "tools/gn/parser.h" |
+#include "tools/gn/scheduler.h" |
+#include "tools/gn/setup.h" |
+#include "tools/gn/source_file.h" |
+#include "tools/gn/tokenizer.h" |
+ |
+namespace commands { |
+ |
+const char kSwitchDumpTree[] = "dump-tree"; |
+const char kSwitchInPlace[] = "in-place"; |
+const char kSwitchStdin[] = "stdin"; |
+ |
+const char kFormat[] = "format"; |
+const char kFormat_HelpShort[] = |
+ "format: Format .gn file. (ALPHA, WILL DESTROY DATA!)"; |
+const char kFormat_Help[] = |
+ "gn format [--dump-tree] [--in-place] [--stdin] BUILD.gn\n" |
+ "\n" |
+ " Formats .gn file to a standard format. THIS IS NOT FULLY IMPLEMENTED\n" |
+ " YET! IT WILL EAT YOUR BEAUTIFUL .GN FILES. AND YOUR LAUNDRY.\n" |
+ " At a minimum, make sure everything is `git commit`d so you can\n" |
+ " `git checkout -f` to recover.\n" |
+ "\n" |
+ "Arguments\n" |
+ " --dump-tree\n" |
+ " For debugging only, dumps the parse tree.\n" |
+ "\n" |
+ " --in-place\n" |
+ " Instead writing the formatted file to stdout, replace the input\n" |
+ " with the formatted output.\n" |
+ "\n" |
+ " --stdin\n" |
+ " Read input from stdin (and write to stdout).\n" |
+ "\n" |
+ "Examples\n" |
+ " gn format //some/BUILD.gn\n" |
+ " gn format some\\BUILD.gn\n" |
+ " gn format /abspath/some/BUILD.gn\n" |
+ " gn format --stdin\n"; |
+ |
+const int kIndentSize = 2; |
+//const int kMaximumWidth = 80; |
+ |
+struct AnnotatedLine; |
+struct AnnotatedLineNode; |
+ |
+enum FormatDecision { |
+ FD_UNFORMATTED, |
+ FD_CONTINUE, |
+ FD_BREAK |
+}; |
+ |
+// ----------------------------------------------------------------------------- |
+// A wrapper around Token storing information about whitespace characters |
+// preceding it. |
+struct FormatToken { |
+ FormatToken() |
+ : newlines_before(0), |
+ decision(FD_UNFORMATTED), |
+ total_length(0), |
+ previous(nullptr), |
+ next(nullptr) {} |
+ |
+ // The token. |
+ Token tok; |
+ |
+ // Number of newlines immediately before the token. This can be used to |
+ // determine what the user wrote in the original code to e.g. leave an empty |
+ // line between two function definitions. |
+ int newlines_before; |
+ |
+ /// Stores the formatting decision for the token once it was made. |
+ FormatDecision decision; |
+ |
+ // The total length of the annotated line up to and including this token. |
+ int total_length; |
+ |
+ FormatToken* previous; |
+ FormatToken* next; |
+ |
+ std::vector<AnnotatedLine*> children; |
+ |
+ |
+ FormatToken* GetPreviousNonComment() const { |
+ FormatToken* tok = previous; |
+ /* TODO |
+ while (tok && tok->Is(COMMENT)) |
+ tok = tok->previous; |
+ */ |
+ return tok; |
+ } |
+}; |
+ |
+// ----------------------------------------------------------------------------- |
+struct ParenState { |
+ ParenState(int indent, int indent_level, int last_space, bool no_line_break) |
+ : indent(indent), |
+ indent_level(indent_level), |
+ last_space(last_space), |
+ no_line_break(no_line_break) {} |
+ |
+ // The position to which a specific parenthesis level needs to be indented. |
+ int indent; |
+ |
+ // The number of indentation levels of the block. |
+ int indent_level; |
+ |
+ // The potision of the last space on each level. |
+ // Used to break in the case of: |
+ // Function(parameter, OtherCall( |
+ // other_parameter)) |
+ int last_space; |
+ |
+ // Break after the next comma. |
+ bool break_before_parameter; |
+ |
+ // Line breaking in this context would break a formatting rule. |
+ bool no_line_break; |
+ |
+ // If the last binary operator on this level was wrapped to th next line. |
+ bool last_operator_wrapped; |
+ |
+ bool operator<(const ParenState& rhs) const { |
+ if (indent != rhs.indent) |
+ return indent < rhs.indent; |
+ if (indent_level != rhs.indent_level) |
+ return indent_level < rhs.indent_level; |
+ if (last_space != rhs.last_space) |
+ return last_space < rhs.last_space; |
+ if (break_before_parameter != rhs.break_before_parameter) |
+ return break_before_parameter; |
+ if (no_line_break != rhs.no_line_break) |
+ return no_line_break; |
+ if (last_operator_wrapped != rhs.last_operator_wrapped) |
+ return last_operator_wrapped; |
+ return false; |
+ } |
+}; |
+ |
+// ----------------------------------------------------------------------------- |
+// The current state when indenting a chunk. |
+// |
+// As the indenting tries different combinations, this is copied by value. |
+struct LineState { |
+ // The number of used columns in the current line. |
+ int column; |
+ |
+ // The token that needs to be formatted next. |
+ FormatToken* next_token; |
+ |
+ // A stack tracing the properties applying to parenthesis levels. |
+ std::vector<ParenState> stack; |
+ |
+ // The indent of the first token. |
+ int first_indent; |
+ |
+ const AnnotatedLine* line; |
+ |
+ // To be able to use LineState in a map. |
+ bool operator<(const LineState& rhs) const { |
+ if (next_token != rhs.next_token) |
+ return next_token < rhs.next_token; |
+ if (column != rhs.column) |
+ return column < rhs.column; |
+ if (first_indent != rhs.first_indent) |
+ return first_indent < rhs.first_indent; |
+ return stack < rhs.stack; |
+ } |
+}; |
+ |
+// ----------------------------------------------------------------------------- |
+class WhitespaceManager { |
+ public: |
+ void ReplaceWhitespace(FormatToken* tok, |
+ int newlines, |
+ int indent_level, |
+ int spaces, |
+ int start_of_token_column); |
+}; |
+ |
+// ----------------------------------------------------------------------------- |
+class ContinuationIndenter { |
+ public: |
+ ContinuationIndenter(WhitespaceManager* whitespaces) |
+ : whitespaces_(whitespaces) {} |
+ |
+ // Get the initial state (the state after place |line|s first token and |
+ // |first_indent|). |
+ LineState GetInitialState(int first_indent, |
+ const AnnotatedLine* line, |
+ bool dry_run); |
+ |
+ // Appends the next token to state and updates information necessary for |
+ // indentation. |
+ // |
+ // Puts the token on the current line if newline is false and adds a line |
+ // break and necessary indentation otherwise. |
+ int AddTokenToState(LineState* state, |
+ bool newline, |
+ int dry_run, |
+ int extra_spaces); |
+ |
+ private: |
+ WhitespaceManager* whitespaces_; |
+}; |
+ |
+int ContinuationIndenter::AddTokenToState(LineState* state, |
+ bool newline, |
+ int dry_run, |
+ int extra_spaces) { |
+ const FormatToken& current = *state.next_token; |
+} |
+ |
+// ----------------------------------------------------------------------------- |
+// An annotated line is a sequence of Token that we would prefer to put on a |
+// single line if there were no column limit. |
+// |
+// The parse tree is flattened down AnnotatedLines and passed to |
+// AnnotatedLineFormatter. |
+struct AnnotatedLine { |
+ AnnotatedLine() : level(0) {} |
+ |
+ // The indent level of the line. |
+ int level; |
+ |
+ FormatToken* first; |
+ FormatToken* last; |
+ |
+ std::vector<AnnotatedLine*> children; |
+ |
+ std::list<AnnotatedLineNode> tokens; |
+}; |
+ |
+// ----------------------------------------------------------------------------- |
+struct AnnotatedLineNode { |
+ AnnotatedLineNode() : tok(nullptr) {} |
+ AnnotatedLineNode(FormatToken* tok) : tok(tok) {} |
+ |
+ FormatToken *tok; |
+ std::vector<AnnotatedLine> children; |
+}; |
+ |
+// ----------------------------------------------------------------------------- |
+class AnnotatedLineFormatter { |
+ public: |
+ AnnotatedLineFormatter(ContinuationIndenter* indenter, |
+ WhitespaceManager* whitespaces) |
+ : indenter_(indenter) /*, whitespaces_(whitespaces)*/ {} |
+ |
+ // Formats a line, and returns the penalty. |
+ int FormatLine(const AnnotatedLine& line, int first_indent, bool dry_run); |
+ |
+ int FormatLines(const std::vector<AnnotatedLine*>& lines, |
+ bool dry_run, |
+ int additional_indent, |
+ bool fix_bad_indentation); |
+ |
+ private: |
+ struct CompareLineStatePointers { |
+ bool operator()(LineState* a, LineState* b) const { return *a < *b; } |
+ }; |
+ |
+ struct StateNode { |
+ StateNode(const LineState& state, bool newline, StateNode* previous) |
+ : state(state), newline(newline), previous(previous) {} |
+ LineState state; |
+ bool newline; |
+ StateNode* previous; |
+ }; |
+ |
+ // A pair of <penalty, count> that is used to prioritize the BFS. |
+ // |
+ // In the case of equal penalties, we want to prefer states that were inserted |
+ // first. During state generation, we make sure that we insert states first |
+ // that break the line as late as possible. |
+ typedef std::pair<unsigned, unsigned> OrderedPenalty; |
+ |
+ // An item in the BFS search queue. The StateNode's State has the given |
+ // OrderedPenalty. |
+ typedef std::pair<OrderedPenalty, StateNode*> QueueItem; |
+ |
+ // The BFS queue type. |
+ typedef std::priority_queue<QueueItem, |
+ std::vector<QueueItem>, |
+ std::greater<QueueItem>> QueueType; |
+ |
+ // Analyze the entire solution space starting from |initial_state|. |
+ // |
+ // This implements Dijkstra's algorithm on the graph that has LineStates as |
+ // nodes. It tries to find the lowest penalty path from |initial_state| to a |
+ // state where all tokens are placed. Returns the penalty. |
+ int AnalyzeSolutionSpace(LineState* initial_state, bool dry_run); |
+ |
+ // Add the following state to the analysis queue. |
+ // |
+ // Assume the current state is previous_node and has been reached with the |
+ // given penalty. Insert a newline if newline is true. |
+ void AddNextStateToQueue(int penalty, |
+ StateNode* previous_node, |
+ bool newline, |
+ int* count, |
+ QueueType* queue); |
+ |
+ // If the state's next token is a } closing a nested block, format the nested |
+ // block before it. |
+ bool FormatChildren(LineState* state, |
+ bool newline, |
+ bool dry_run, |
+ int* penalty); |
+ |
+ ContinuationIndenter* indenter_; |
+ //WhitespaceManager* whitespaces_; |
+}; |
+ |
+int AnnotatedLineFormatter::FormatLines( |
+ const std::vector<AnnotatedLine*>& lines, |
+ bool dry_run, |
+ int additional_indent, |
+ bool fix_bad_indentation) { |
+ return 0; |
+} |
+ |
+int AnnotatedLineFormatter::FormatLine(const AnnotatedLine& line, |
+ int first_indent, |
+ bool dry_run) { |
+ LineState state = indenter_->GetInitialState(first_indent, &line, dry_run); |
+ return AnalyzeSolutionSpace(&state, dry_run); |
+} |
+ |
+int AnnotatedLineFormatter::AnalyzeSolutionSpace(LineState* initial_state, |
+ bool dry_run) { |
+ std::set<LineState*, CompareLineStatePointers> seen; |
+ |
+ // Increasing count of StateNode items we have created. This is used to |
+ // create a deterministic order independent of the container. |
+ int count = 0; |
+ QueueType queue; |
+ |
+ // Insert start element into queue. |
+ StateNode* node = new StateNode(*initial_state, false, nullptr); |
+ queue.push(QueueItem(OrderedPenalty(0, count), node)); |
+ ++count; |
+ |
+ int penalty = 0; |
+ |
+ // While not empty, take first element and follow edges. |
+ while (!queue.empty()) { |
+ penalty = queue.top().first.first; |
+ StateNode* node = queue.top().second; |
+ if (!node->state.next_token) { |
+ fprintf(stderr, "\n---\nPenalty for line: %d\n", penalty); |
+ break; |
+ } |
+ queue.pop(); |
+ |
+ if (!seen.insert(&node->state).second) { |
+ // State already examinied with lower penalty. |
+ continue; |
+ } |
+ |
+ FormatDecision last_format = node->state.next_token->decision; |
+ if (last_format == FD_UNFORMATTED || last_format == FD_CONTINUE) |
+ AddNextStateToQueue(penalty, node, false, &count, &queue); |
+ if (last_format == FD_UNFORMATTED || last_format == FD_BREAK) |
+ AddNextStateToQueue(penalty, node, true, &count, &queue); |
+ } |
+ |
+ if (queue.empty()) { |
+ fprintf(stderr, "Could not find a solution.\n"); |
+ return 0; |
+ } |
+ |
+ if (!dry_run) { |
+ fprintf(stderr, "todo; ReconstructPath\n"); |
+ } |
+ |
+ fprintf(stderr, "Total number of analyzed states: %d\n---\n", count); |
+ |
+ return penalty; |
+} |
+ |
+void AnnotatedLineFormatter::AddNextStateToQueue(int penalty, |
+ StateNode* previous_node, |
+ bool newline, |
+ int* count, |
+ QueueType* queue) { |
+ StateNode* node = |
+ new StateNode(previous_node->state, newline, previous_node); |
+ if (!FormatChildren(&node->state, newline, true, &penalty)) |
+ return; |
+ |
+ penalty = indenter_->AddTokenToState(&node->state, newline, true, 0); |
+ queue->push(QueueItem(OrderedPenalty(penalty, *count), node)); |
+ ++(*count); |
+} |
+ |
+bool AnnotatedLineFormatter::FormatChildren(LineState* state, |
+ bool newline, |
+ bool dry_run, |
+ int* penalty) { |
+ FormatToken* previous = state->next_token->previous; |
+ const FormatToken* lbrace = state->next_token->GetPreviousNonComment(); |
+ if (!lbrace || previous->children.size() == 0 /* TODO: stuff */) |
+ return true; |
+ |
+ if (newline) { |
+ int additional_indent = |
+ state->first_indent - state->line->level * kIndentSize; |
+ penalty += |
+ FormatLines(previous->children, dry_run, additional_indent, true); |
+ return true; |
+ } |
+ |
+ // Can't merge multiple statements into a single line. |
+ if (previous->children.size() > 1) |
+ return false; |
+ |
+ // Can't merge into one line if this line ends on a comment. |
+ //if (previous.is(COMMENT)) |
+ //return false; |
+ |
+ // TODO: more |
+ |
+ penalty += |
+ FormatLine(*previous->children[0], state->column + 1, dry_run); |
+ state->column += 1 + previous->children[0]->last->total_length; |
+ return true; |
+} |
+ |
+// ----------------------------------------------------------------------------- |
+void DoFormat(const ParseNode* root, bool dump_tree, std::string* output) { |
+ *output = std::string(); |
+} |
+ |
+std::string ReadStdin() { |
+ static const int kBufferSize = 256; |
+ char buffer[kBufferSize]; |
+ std::string result; |
+ while (true) { |
+ char* input = NULL; |
+ input = fgets(buffer, kBufferSize, stdin); |
+ if (input == NULL && feof(stdin)) |
+ return result; |
+ int length = static_cast<int>(strlen(buffer)); |
+ if (length == 0) |
+ return result; |
+ else |
+ result += std::string(buffer, length); |
+ } |
+} |
+ |
+ |
+ |
+ |
+bool FormatFileToString(Setup* setup, |
+ const SourceFile& file, |
+ bool dump_tree, |
+ std::string* output) { |
+ Err err; |
+ const ParseNode* parse_node = |
+ setup->scheduler().input_file_manager()->SyncLoadFile( |
+ LocationRange(), &setup->build_settings(), file, &err); |
+ if (err.has_error()) { |
+ err.PrintToStdout(); |
+ return false; |
+ } |
+ DoFormat(parse_node, dump_tree, output); |
+ return true; |
+} |
+ |
+bool FormatStringToString(const std::string& input, |
+ bool dump_tree, |
+ std::string* output) { |
+ SourceFile source_file; |
+ InputFile file(source_file); |
+ file.SetContents(input); |
+ Err err; |
+ // Tokenize. |
+ std::vector<Token> tokens = Tokenizer::Tokenize(&file, &err); |
+ if (err.has_error()) { |
+ err.PrintToStdout(); |
+ return false; |
+ } |
+ |
+ // Parse. |
+ scoped_ptr<ParseNode> parse_node = Parser::Parse(tokens, &err); |
+ if (err.has_error()) { |
+ err.PrintToStdout(); |
+ return false; |
+ } |
+ |
+ DoFormat(parse_node.get(), dump_tree, output); |
+ return true; |
+} |
+ |
+int RunFormat(const std::vector<std::string>& args) { |
+ bool dump_tree = |
+ base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchDumpTree); |
+ |
+ bool from_stdin = |
+ base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchStdin); |
+ |
+ if (from_stdin) { |
+ if (args.size() != 0) { |
+ Err(Location(), "Expecting no arguments when reading from stdin.\n") |
+ .PrintToStdout(); |
+ return 1; |
+ } |
+ std::string input = ReadStdin(); |
+ std::string output; |
+ if (!FormatStringToString(input, dump_tree, &output)) |
+ return 1; |
+ printf("%s", output.c_str()); |
+ return 0; |
+ } |
+ |
+ // TODO(scottmg): Eventually, this should be a list/spec of files, and they |
+ // should all be done in parallel. |
+ if (args.size() != 1) { |
+ Err(Location(), "Expecting exactly one argument, see `gn help format`.\n") |
+ .PrintToStdout(); |
+ return 1; |
+ } |
+ |
+ Setup setup; |
+ SourceDir source_dir = |
+ SourceDirForCurrentDirectory(setup.build_settings().root_path()); |
+ SourceFile file = source_dir.ResolveRelativeFile(args[0], |
+ setup.build_settings().root_path_utf8()); |
+ |
+ std::string output_string; |
+ if (FormatFileToString(&setup, file, dump_tree, &output_string)) { |
+ bool in_place = |
+ base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchInPlace); |
+ if (in_place) { |
+ base::FilePath to_write = setup.build_settings().GetFullPath(file); |
+ if (base::WriteFile(to_write, |
+ output_string.data(), |
+ static_cast<int>(output_string.size())) == -1) { |
+ Err(Location(), |
+ std::string("Failed to write formatted output back to \"") + |
+ to_write.AsUTF8Unsafe() + std::string("\".")).PrintToStdout(); |
+ return 1; |
+ } |
+ printf("Wrote formatted to '%s'.\n", to_write.AsUTF8Unsafe().c_str()); |
+ } else { |
+ printf("%s", output_string.c_str()); |
+ } |
+ } |
+ |
+ return 0; |
+} |
+ |
+} // namespace commands |