| // Copyright 2017-2020 The Verible Authors. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #ifndef VERIBLE_COMMON_FORMATTING_STATE_NODE_H_ | |
| #define VERIBLE_COMMON_FORMATTING_STATE_NODE_H_ | |
| #include <cstddef> | |
| #include <iosfwd> | |
| #include <iterator> | |
| #include <memory> | |
| #include <stack> | |
| #include <vector> | |
| #include "common/formatting/basic_format_style.h" | |
| #include "common/formatting/format_token.h" | |
| #include "common/formatting/unwrapped_line.h" | |
| #include "common/util/container_iterator_range.h" | |
| namespace verible { | |
| // A StateNode is used to keep a formatting state as the tokens of an | |
| // UnwrappedLine are searched left to right. Each StateNode represents one | |
| // formatting decision: wrap or not-wrap. Each StateNode maintains a pointer | |
| // to its parent state, which is used for backtracking once a solution | |
| // is reached. StateNode is language-agnostic. | |
| // StateNode is purely an implementation detail of line_wrap_searcher.cc. | |
| struct StateNode { | |
| typedef std::vector<PreFormatToken> path_type; | |
| typedef container_iterator_range<path_type::const_iterator> range_type; | |
| // The StateNode that has an edge to this StateNode, to backtrack once a final | |
| // state is reached. | |
| std::shared_ptr<const StateNode> prev_state; | |
| // Iterator range marking the unexplored decisions beyond the current token. | |
| // TODO(fangism): make the iterator type a template parameter. Might help | |
| // with mocking and testing. | |
| range_type undecided_path; | |
| // Explores one of the SpacingDecision choices. | |
| SpacingDecision spacing_choice = SpacingDecision::Preserve; | |
| // The current column position, this increases with every token that is | |
| // appended onto the current line, and resets to the indentation level | |
| // (plus wrapping) with every line break. | |
| int current_column = 0; | |
| // The total cost along this decision path. This monotonically increases | |
| // with each decision explored. | |
| int cumulative_cost = 0; | |
| // Kludge: in the event of a wrapped multi-line token, the current_column | |
| // position and the raw token text length are insufficient to infer what | |
| // the spaces before the format token are because current_column is only | |
| // based on the substring of text after the last newline. To be able to | |
| // reconstruct the pre-format-token spacing, we must record it. | |
| // This is initialized with a sentinel value of -1 as a safety precaution | |
| // to guard against accidental use. | |
| int wrap_multiline_token_spaces_before = -1; | |
| // Keeps track of column positions of every level of wrapping, as determined | |
| // by balanced group delimiters such as braces, brackets, parentheses. | |
| // These column positions correspond to either the current indentation level | |
| // plus wrapping or the column position of the nearest group-opening | |
| // delimiter. | |
| // TODO(b/135730018): re-implement to minimize copying of stacks. | |
| // For example, use pointer or iterator to previous stack update. | |
| std::stack<int> wrap_column_positions; | |
| // Constructor for the root node of the search path, with no parent. | |
| // This automatically places the first token at the beginning of a new line | |
| // for position tracking purposes. | |
| // If the UnwrappedLine has only one token or is empty, the initial state | |
| // will be Done(). | |
| StateNode(const UnwrappedLine& uwline, const BasicFormatStyle& style); | |
| // Constructor for nodes that represent new wrap decision trees to explore. | |
| // 'spacing_choice' reflects the decision being explored, e.g. append, wrap, | |
| // preserve. | |
| StateNode(const std::shared_ptr<const StateNode>& parent, | |
| const BasicFormatStyle& style, SpacingDecision spacing_choice); | |
| // Returns true when the undecided_path is empty. | |
| // The search is over when there are no more decisions to explore. | |
| bool Done() const { return undecided_path.begin() == undecided_path.end(); } | |
| // Returns a reference to the token that is being acted upon in this state. | |
| const PreFormatToken& GetCurrentToken() const { | |
| // The undecided_path always starts at the position after the current token. | |
| return *(undecided_path.begin() - 1); | |
| } | |
| // Returns a reference to the token that considered for wrapping vs. | |
| // appending. | |
| const PreFormatToken& GetNextToken() const { | |
| // The undecided_path always starts at the position after the current token. | |
| return *undecided_path.begin(); | |
| } | |
| // Returns pointer to previous state before this decision node. | |
| // This functions as a forward-iterator going up the state ancestry chain. | |
| const StateNode* next() const { return prev_state.get(); } | |
| // Returns true if this state was initialized with an unwrapped line and | |
| // has no parent state. | |
| bool IsRootState() const { return prev_state == nullptr; } | |
| // Returns the total number of nodes in state ancestry, including itself. | |
| // This occurs in O(N) time, and is only suitable for testing/debug. | |
| size_t Depth() const { | |
| size_t depth = 1; | |
| const auto* iter = this; | |
| while (!iter->IsRootState()) { | |
| ++depth; | |
| iter = iter->prev_state.get(); | |
| } | |
| return depth; | |
| } | |
| // Produce next state by appending a token if the result stays under the | |
| // column limit, or breaking onto a new line if required. | |
| static std::shared_ptr<const StateNode> AppendIfItFits( | |
| const std::shared_ptr<const StateNode>& current_state, | |
| const BasicFormatStyle& style); | |
| // Repeatedly apply AppendIfItFits() until Done() with formatting. | |
| // TODO(b/134711965): We may want a variant that preserves spaces too. | |
| static std::shared_ptr<const StateNode> QuickFinish( | |
| const std::shared_ptr<const StateNode>& current_state, | |
| const BasicFormatStyle& style); | |
| // Comparator provides an ordering of which paths should be explored | |
| // when maintained in a priority queue. For Dijsktra-style algorithms, | |
| // we want to explore the min-cost paths first. | |
| bool operator<(const StateNode& r) const { | |
| return cumulative_cost < r.cumulative_cost || | |
| // TODO(b/145558510): Favor solutions that use fewer lines. | |
| // To do that would require counting number of wrap decisions, | |
| // which is slow, unless we keep track of that number in StateNode. | |
| // tie-breaker: All else being equal, use terminal column position. | |
| (cumulative_cost == r.cumulative_cost && | |
| current_column < r.current_column); | |
| } | |
| // Applies decisions from a path search to the set of format tokens in a | |
| // FormattedExcerpt. 'this' is the last decision in a tree that encodes | |
| // wrap decisions (through ancestry chain: prev_state) all the way back to | |
| // the first token in the original UnwrappedLine (that was used to | |
| // initialize the root state). | |
| void ReconstructFormatDecisions(FormattedExcerpt*) const; | |
| private: | |
| const PreFormatToken& _GetPreviousToken() const; | |
| int _UpdateColumnPosition(); | |
| void _UpdateCumulativeCost(const BasicFormatStyle&, int column_for_penalty); | |
| void _OpenGroupBalance(const BasicFormatStyle&); | |
| void _CloseGroupBalance(); | |
| }; | |
| // Human-readable representation for debugging only. | |
| std::ostream& operator<<(std::ostream&, const StateNode&); | |
| } // namespace verible | |
| #endif // VERIBLE_COMMON_FORMATTING_STATE_NODE_H_ |