blob: 706c0a0660619e834f72812c70ece74f685dd943 [file] [log] [blame]
// 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_