| // Copyright 2017-2021 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. |
| |
| // Implementation details of layout_optimizer.cc exported for tests. |
| |
| #ifndef VERIBLE_VERILOG_FORMATTING_LAYOUT_OPTIMIZER_INTERNAL_H_ |
| #define VERIBLE_VERILOG_FORMATTING_LAYOUT_OPTIMIZER_INTERNAL_H_ |
| |
| #include <algorithm> |
| #include <iterator> |
| #include <ostream> |
| #include <type_traits> |
| #include <vector> |
| |
| #include "absl/container/fixed_array.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/str_join.h" |
| #include "common/formatting/basic_format_style.h" |
| #include "common/formatting/token_partition_tree.h" |
| #include "common/formatting/unwrapped_line.h" |
| #include "common/util/tree_operations.h" |
| #include "common/util/vector_tree.h" |
| |
| namespace verible { |
| |
| // LayoutItem type |
| enum class LayoutType { |
| // Single line. LayoutItem of this type is always a leaf in LayoutTree. |
| kLine, |
| |
| // Joins child items horizontally. See also: |
| // LayoutFunctionFactory::Juxtaposition. |
| kJuxtaposition, |
| |
| // Stacks child items vertically. See also: LayoutFunctionFactory::Stack. |
| kStack, |
| }; |
| |
| std::ostream& operator<<(std::ostream& stream, LayoutType type); |
| |
| // LayoutTree node data |
| class LayoutItem { |
| public: |
| // Prevent creation of uninitialized LayoutItem |
| LayoutItem() = delete; |
| |
| LayoutItem(LayoutType type, int spacing, bool must_wrap, int indentation = 0) |
| : type_(type), |
| indentation_(indentation), |
| spaces_before_(spacing), |
| must_wrap_(must_wrap) { |
| CHECK_GE(indentation_, 0); |
| CHECK_GE(spaces_before_, 0); |
| } |
| |
| // Creates Line item from UnwrappedLine. |
| explicit LayoutItem(const UnwrappedLine& uwline, int indentation = 0) |
| : type_(LayoutType::kLine), |
| indentation_(indentation), |
| tokens_(uwline.TokensRange()), |
| spaces_before_(SpacesRequiredBeforeUnwrappedLine(uwline)), |
| must_wrap_(UnwrappedLineMustWrap(uwline)) { |
| CHECK_GE(indentation_, 0); |
| CHECK_GE(spaces_before_, 0); |
| } |
| |
| // Creates Line item from UnwrappedLine. |
| explicit LayoutItem(const UnwrappedLine& uwline, bool must_wrap, |
| int indentation) |
| : type_(LayoutType::kLine), |
| indentation_(indentation), |
| tokens_(uwline.TokensRange()), |
| spaces_before_(SpacesRequiredBeforeUnwrappedLine(uwline)), |
| must_wrap_(must_wrap) { |
| CHECK_GE(indentation_, 0); |
| CHECK_GE(spaces_before_, 0); |
| } |
| |
| // Multiple LayoutFunctionSegments can store copies of the same layout. |
| // The objects are copied mostly in LayoutFunctionFactory::* functions. |
| LayoutItem(const LayoutItem&) = default; |
| LayoutItem& operator=(const LayoutItem&) = default; |
| |
| LayoutItem(LayoutItem&&) = default; |
| LayoutItem& operator=(LayoutItem&&) = default; |
| |
| LayoutType Type() const { return type_; } |
| |
| // Indentation used for a layout when it is placed at the beginning of a line. |
| // Effective indentation in this case is a sum of the item's and its |
| // ancestors' indetation |
| int IndentationSpaces() const { return indentation_; } |
| |
| // Sets indentation used for a layout when it is placed at the beginning of |
| // a line. |
| void SetIndentationSpaces(int indent) { indentation_ = indent; } |
| |
| // Returns number of spaces required before the first token. The spaces are |
| // used when the layout is appended to a non-empty line. |
| int SpacesBefore() const { return spaces_before_; } |
| |
| // Returns whether to force line break just before this layout. |
| bool MustWrap() const { return must_wrap_; } |
| |
| // Sets whether to force line break just before this layout. |
| void SetMustWrap(bool must_wrap) { must_wrap_ = must_wrap; } |
| |
| // Returns textual representation of spanned tokens for Line items, empty |
| // string for other item types. |
| std::string Text() const { |
| return absl::StrJoin(tokens_, " ", |
| [](std::string* out, const PreFormatToken& token) { |
| absl::StrAppend(out, token.Text()); |
| }); |
| } |
| |
| // Returns length of the line in columns. |
| // Can be called only on Line items. |
| int Length() const { |
| CHECK_EQ(type_, LayoutType::kLine); |
| |
| if (tokens_.empty()) return 0; |
| int len = 0; |
| for (const auto& token : tokens_) { |
| // TODO (mglb): support all possible break_decisions |
| len += token.before.spaces_required; |
| if (const auto line_break_pos = token.Text().find('\n'); |
| line_break_pos != absl::string_view::npos) { |
| // Multiline tokens are not really supported. |
| // Use number of characters up to the first line break. |
| len += line_break_pos; |
| DVLOG(5) << __FUNCTION__ << ": WARNING: Token contains '\\n':\n" |
| << *token.token; |
| } else { |
| len += token.Length(); |
| } |
| } |
| len -= tokens_.front().before.spaces_required; |
| return len; |
| } |
| |
| // Returns tokens range spanned by the Line item. |
| // Can be called only on Line items. |
| FormatTokenRange TokensRange() const { |
| CHECK_EQ(type_, LayoutType::kLine); |
| return tokens_; |
| } |
| |
| friend bool operator==(const LayoutItem& lhs, const LayoutItem& rhs) { |
| return (lhs.type_ == rhs.type_ && lhs.indentation_ == rhs.indentation_ && |
| lhs.tokens_ == rhs.tokens_ && |
| lhs.spaces_before_ == rhs.spaces_before_ && |
| lhs.must_wrap_ == rhs.must_wrap_); |
| } |
| |
| private: |
| static bool UnwrappedLineMustWrap(const UnwrappedLine& uwline) { |
| if (uwline.TokensRange().empty()) return false; |
| |
| const auto policy = uwline.PartitionPolicy(); |
| if (policy == PartitionPolicyEnum::kInline) return false; |
| if (policy == PartitionPolicyEnum::kAlreadyFormatted) return true; |
| |
| auto break_decision = uwline.TokensRange().front().before.break_decision; |
| return (break_decision == SpacingOptions::MustWrap); |
| } |
| |
| static int SpacesRequiredBeforeUnwrappedLine(const UnwrappedLine& uwline) { |
| const auto tokens = uwline.TokensRange(); |
| const auto policy = uwline.PartitionPolicy(); |
| const auto indentation = uwline.IndentationSpaces(); |
| |
| if (policy == PartitionPolicyEnum::kInline) return indentation; |
| if (tokens.empty()) return 0; |
| return tokens.front().before.spaces_required; |
| } |
| |
| LayoutType type_; |
| int indentation_; |
| FormatTokenRange tokens_; |
| int spaces_before_; |
| bool must_wrap_; |
| }; |
| |
| std::ostream& operator<<(std::ostream& stream, const LayoutItem& layout); |
| |
| // Intermediate partition tree layout |
| using LayoutTree = VectorTree<LayoutItem>; |
| |
| // Single segment of LayoutFunction |
| // Maps starting column to a linear cost function and its optimal layout. |
| struct LayoutFunctionSegment { |
| // Starting column. |
| // AKA: knot. |
| int column; |
| |
| // Optimal layout for an interval starting at the column. |
| // AKA: layout expression |
| LayoutTree layout; |
| |
| // Width of the last line of the layout in columns. |
| int span; |
| |
| // Intercept (a constant) of linear cost function. |
| float intercept; |
| // Gradient (rate of change) of linear cost function. |
| int gradient; |
| |
| // Returns cost of placing the layout at 'margin' column. |
| float CostAt(int margin) const { |
| CHECK_GE(margin, 0); |
| CHECK_GE(margin, column); |
| return intercept + gradient * (margin - column); |
| } |
| }; |
| |
| template <bool IsConstIterator> |
| class LayoutFunctionIterator; |
| |
| // Piecewise-linear layout function. |
| // |
| // The layout function represents one or more layouts for a single fragment of |
| // code and a cost function used for picking the most optimal layout. |
| // |
| // The class is a set containing LayoutFunctionSegments. Each segment starts at |
| // its starting column and ends at the next segment's starting column. The last |
| // segment spans up to infinity. |
| // |
| // AKA: KnotSet, Block |
| class LayoutFunction { |
| public: |
| using iterator = LayoutFunctionIterator<false>; |
| using const_iterator = LayoutFunctionIterator<true>; |
| |
| LayoutFunction() = default; |
| LayoutFunction(std::initializer_list<LayoutFunctionSegment> segments) |
| : segments_(segments) { |
| CHECK(AreSegmentsSorted()); |
| if (!segments_.empty()) CHECK_EQ(segments_.front().column, 0); |
| } |
| |
| LayoutFunction(LayoutFunction&&) = default; |
| LayoutFunction& operator=(LayoutFunction&&) = default; |
| |
| LayoutFunction(const LayoutFunction&) = default; |
| LayoutFunction& operator=(const LayoutFunction&) = default; |
| |
| void push_back(const LayoutFunctionSegment& segment) { |
| if (!segments_.empty()) |
| CHECK_LT(segments_.back().column, segment.column); |
| else |
| CHECK_EQ(segment.column, 0); |
| segments_.push_back(segment); |
| } |
| void push_back(LayoutFunctionSegment&& segment) { |
| if (!segments_.empty()) |
| CHECK_LT(segments_.back().column, segment.column); |
| else |
| CHECK_EQ(segment.column, 0); |
| segments_.push_back(segment); |
| } |
| |
| bool empty() const { return segments_.empty(); } |
| |
| int size() const { return segments_.size(); } |
| |
| iterator begin(); |
| const_iterator begin() const; |
| |
| iterator end(); |
| const_iterator end() const; |
| |
| // Returns iterator pointing to a segment starting at or to the left of |
| // 'column'. |
| // AKA: x- |
| const_iterator AtOrToTheLeftOf(int column) const; |
| |
| LayoutFunctionSegment& front() { return segments_.front(); } |
| const LayoutFunctionSegment& front() const { return segments_.front(); } |
| |
| LayoutFunctionSegment& back() { return segments_.back(); } |
| const LayoutFunctionSegment& back() const { return segments_.back(); } |
| |
| const LayoutFunctionSegment& operator[](size_t index) const { |
| CHECK_GE(index, 0); |
| CHECK_LT(index, segments_.size()); |
| return segments_[index]; |
| } |
| LayoutFunctionSegment& operator[](size_t index) { |
| CHECK_GE(index, 0); |
| CHECK_LT(index, segments_.size()); |
| return segments_[index]; |
| } |
| |
| // Returns whether to force line break just before this layout. |
| bool MustWrap() const { |
| if (empty()) return false; |
| const bool must_wrap = segments_.front().layout.Value().MustWrap(); |
| // If for some reason not all layouts have the same "MustWrap" status, it |
| // should be taken into account in the code that uses this method. This |
| // shouldn't be the case, as every layout should wrap the same token range. |
| CHECK(std::all_of(segments_.begin(), segments_.end(), |
| [must_wrap](const auto& segment) { |
| return segment.layout.Value().MustWrap() == must_wrap; |
| })); |
| return must_wrap; |
| } |
| |
| // Sets whether to force line break just before this layout. |
| void SetMustWrap(bool must_wrap) { |
| for (auto& segment : segments_) |
| segment.layout.Value().SetMustWrap(must_wrap); |
| } |
| |
| private: |
| bool AreSegmentsSorted() const { |
| return std::is_sorted( |
| segments_.begin(), segments_.end(), |
| [](const LayoutFunctionSegment& a, const LayoutFunctionSegment& b) { |
| return a.column < b.column; |
| }); |
| } |
| // Elements in 'segments_' must have unique columns and be sorted by column. |
| // The first segment must start at column 0. |
| // std::set would be more appropriate generally, but due to really |
| // small amount of elements the container has to hold and ordered inserts, it |
| // probably wouldn't help in anything. |
| std::vector<LayoutFunctionSegment> segments_; |
| }; |
| |
| template <bool IsConst, typename T> |
| using ConditionalConst = std::conditional_t<IsConst, std::add_const_t<T>, T>; |
| |
| // Iterator used by LayoutFunction. |
| template <bool IsConstIterator> |
| class LayoutFunctionIterator { |
| using iterator = LayoutFunctionIterator<IsConstIterator>; |
| using container = ConditionalConst<IsConstIterator, LayoutFunction>; |
| // Make friends with the-other-constness iterator (for comparison operators) |
| friend class LayoutFunctionIterator<!IsConstIterator>; |
| |
| public: |
| LayoutFunctionIterator() = default; |
| |
| explicit LayoutFunctionIterator(container& layout_function, int index = 0) |
| : lf_(&layout_function), index_(index) { |
| CHECK_LE(index_, lf_->size()); |
| } |
| |
| LayoutFunctionIterator(const iterator&) = default; |
| LayoutFunctionIterator& operator=(const iterator&) = default; |
| |
| // Helper methods |
| |
| // Returns reference to iterated container |
| container& Container() const { return *lf_; } |
| |
| // Returns index of current element |
| int Index() const { return index_; } |
| |
| // Return whether iterator points to container's end() |
| bool IsEnd() const { return index_ == lf_->size(); } |
| |
| // Moves iterator to a segment starting at or to the left of 'column'. |
| void MoveToKnotAtOrToTheLeftOf(int column) { |
| CHECK_GE(column, 0); |
| if (Container().empty()) return; |
| CHECK_EQ(Container().front().column, 0); |
| |
| auto& this_ref = *this; |
| if (this_ref->column > column) { |
| while (this_ref->column > column) --this_ref; |
| } else { |
| // Find first segment to the right of the 'column'... |
| while (!IsEnd() && this_ref->column <= column) ++this_ref; |
| // ... and go one segment back. |
| --this_ref; |
| } |
| } |
| |
| // RandomAccessIterator interface |
| |
| using iterator_category = std::random_access_iterator_tag; |
| using difference_type = std::ptrdiff_t; |
| using value_type = LayoutFunctionSegment; |
| using pointer = ConditionalConst<IsConstIterator, LayoutFunctionSegment>*; |
| using reference = ConditionalConst<IsConstIterator, LayoutFunctionSegment>&; |
| |
| reference operator*() const { return (*lf_)[index_]; } |
| |
| pointer operator->() const { return &(*lf_)[index_]; } |
| |
| reference operator[](size_t index) const { |
| CHECK_LT(index, lf_->size() - index_); |
| return (*lf_)[index_ + index]; |
| } |
| |
| iterator& operator+=(difference_type rhs) { |
| CHECK_LE(rhs, lf_->size() - index_); |
| index_ += rhs; |
| return *this; |
| } |
| iterator& operator-=(difference_type rhs) { |
| CHECK_LE(rhs, index_); |
| index_ -= rhs; |
| return *this; |
| } |
| |
| iterator& operator++() { return *this += 1; } |
| |
| iterator& operator--() { return *this -= 1; } |
| |
| iterator operator++(int) { |
| auto tmp = *this; |
| ++(*this); |
| return tmp; |
| } |
| |
| iterator operator--(int) { |
| auto tmp = *this; |
| --(*this); |
| return tmp; |
| } |
| |
| iterator operator+(difference_type rhs) const { |
| return iterator(*lf_, index_ + rhs); |
| } |
| iterator operator-(difference_type rhs) const { |
| return iterator(*lf_, index_ - rhs); |
| } |
| |
| friend iterator operator+(difference_type lhs, const iterator& rhs) { |
| return iterator(*rhs.lf_, lhs + rhs.index_); |
| } |
| friend iterator operator-(difference_type lhs, const iterator& rhs) { |
| return iterator(*rhs.lf_, lhs - rhs.index_); |
| } |
| |
| template <bool RhsIsConst> |
| difference_type operator+( |
| const LayoutFunctionIterator<RhsIsConst>& rhs) const { |
| return this->index_ + rhs.index_; |
| } |
| template <bool RhsIsConst> |
| difference_type operator-( |
| const LayoutFunctionIterator<RhsIsConst>& rhs) const { |
| return this->index_ - rhs.index_; |
| } |
| |
| template <bool RhsIsConst> |
| bool operator==(const LayoutFunctionIterator<RhsIsConst>& rhs) const { |
| return (lf_ == rhs.lf_) && (index_ == rhs.index_); |
| } |
| template <bool RhsIsConst> |
| bool operator<(const LayoutFunctionIterator<RhsIsConst>& rhs) const { |
| return (lf_ == rhs.lf_) && (index_ < rhs.index_); |
| } |
| template <bool RhsIsConst> |
| bool operator>(const LayoutFunctionIterator<RhsIsConst>& rhs) const { |
| return (lf_ == rhs.lf_) && (index_ > rhs.index_); |
| } |
| template <bool RhsIsConst> |
| bool operator!=(const LayoutFunctionIterator<RhsIsConst>& rhs) const { |
| return !(*this == rhs); |
| } |
| template <bool RhsIsConst> |
| bool operator<=(const LayoutFunctionIterator<RhsIsConst>& rhs) const { |
| return !(*this > rhs); |
| } |
| template <bool RhsIsConst> |
| bool operator>=(const LayoutFunctionIterator<RhsIsConst>& rhs) const { |
| return !(*this < rhs); |
| } |
| |
| private: |
| container* lf_; |
| int index_; |
| }; |
| |
| std::ostream& operator<<(std::ostream& stream, |
| const LayoutFunctionSegment& segment); |
| |
| std::ostream& operator<<(std::ostream& stream, const LayoutFunction& lf); |
| |
| template <bool IsConstIterator> |
| std::ostream& operator<<(std::ostream& stream, |
| const LayoutFunctionIterator<IsConstIterator>& it) { |
| return stream << &it.Container() << "[" << it.Index() << "/" |
| << it.Container().size() << "]"; |
| } |
| |
| template <typename Iterator, typename ValueType> |
| inline constexpr bool IsIteratorDereferencingTo = std::is_same_v< |
| std::remove_const_t<typename std::iterator_traits<Iterator>::value_type>, |
| ValueType>; |
| |
| // Methods for creating and combining LayoutFunctions |
| class LayoutFunctionFactory { |
| public: |
| explicit LayoutFunctionFactory(const BasicFormatStyle& style) |
| : style_(style) {} |
| |
| // Creates LayoutFunction for a single line from UnwrappedLine 'uwline'. |
| LayoutFunction Line(const UnwrappedLine& uwline) const; |
| |
| // Creates LayoutFunction from a single line with tokens wrapped using Wrap(). |
| LayoutFunction WrappedLine(const UnwrappedLine& uwline) const; |
| |
| // Combines two or more layouts vertically. |
| // All combined layouts start at the same column. The first line of layout |
| // n+1 is immediately below the last line of layout n. |
| LayoutFunction Stack(std::initializer_list<LayoutFunction> lfs) const { |
| return Stack(lfs.begin(), lfs.end()); |
| } |
| |
| // See Stack(std::initializer_list<LayoutFunction> lfs). |
| // |
| // Iterator: iterator type that dereferences to LayoutFunction. |
| template <class Iterator> |
| LayoutFunction Stack(const Iterator begin, const Iterator end) const { |
| static_assert(IsIteratorDereferencingTo<Iterator, LayoutFunction>, |
| "Iterator's value type must be LayoutFunction."); |
| const auto lfs = make_container_range(begin, end); |
| |
| if (lfs.empty()) return LayoutFunction(); |
| if (lfs.size() == 1) return lfs.front(); |
| |
| // Create a segment iterator for each LayoutFunction. |
| auto segments = |
| absl::FixedArray<LayoutFunction::const_iterator>(lfs.size()); |
| std::transform(lfs.begin(), lfs.end(), segments.begin(), |
| [](const LayoutFunction& lf) { |
| CHECK(!lf.empty()); |
| return lf.begin(); |
| }); |
| |
| return Stack(&segments); |
| } |
| |
| // Combines two or more layouts so that the layout N+1 is directy to the |
| // right of the last line of layout N. |
| // |
| // EXAMPLE: |
| // |
| // Layout 1: |
| // First First First First First First |
| // First First First |
| // |
| // Layout 2: |
| // Second Second Second |
| // Second Second |
| // |
| // Juxtaposition: |
| // First First First First First First |
| // First First First Second Second Second |
| // Second Second |
| LayoutFunction Juxtaposition( |
| std::initializer_list<LayoutFunction> lfs) const { |
| return Juxtaposition(lfs.begin(), lfs.end()); |
| } |
| |
| // See Juxtaposition(std::initializer_list<LayoutFunction> lfs). |
| // |
| // Iterator: iterator type that dereferences to LayoutFunction. |
| template <class Iterator> |
| LayoutFunction Juxtaposition(Iterator begin, Iterator end) const { |
| static_assert(IsIteratorDereferencingTo<Iterator, LayoutFunction>, |
| "Iterator's value type must be LayoutFunction."); |
| |
| auto lfs_container = make_container_range(begin, end); |
| |
| if (lfs_container.empty()) return LayoutFunction(); |
| if (lfs_container.size() == 1) return lfs_container.front(); |
| |
| LayoutFunction incremental = lfs_container.front(); |
| lfs_container.pop_front(); |
| for (auto& lf : lfs_container) { |
| incremental = Juxtaposition(incremental, lf); |
| } |
| |
| return incremental; |
| } |
| |
| // Creates the piecewise minimum function of a set of LayoutFunctions. |
| // |
| // The combinator is intended to choose optimal layout from a set of |
| // different layouts of the same code fragment. |
| // |
| // When two layouts have the same cost, the function favors the layout with |
| // lower gradient. When gradients are equal too, earlier element is used. |
| LayoutFunction Choice(std::initializer_list<LayoutFunction> lfs) const { |
| return Choice(lfs.begin(), lfs.end()); |
| } |
| |
| // See Choice(std::initializer_list<LayoutFunction> lfs). |
| // |
| // Iterator: iterator type that dereferences to LayoutFunction. |
| template <class Iterator> |
| LayoutFunction Choice(const Iterator begin, const Iterator end) const { |
| static_assert(IsIteratorDereferencingTo<Iterator, LayoutFunction>, |
| "Iterator's value type must be LayoutFunction."); |
| const auto lfs = make_container_range(begin, end); |
| |
| if (lfs.empty()) return LayoutFunction(); |
| if (lfs.size() == 1) return lfs.front(); |
| |
| // Create a segment iterator for each LayoutFunction. |
| auto segments = |
| absl::FixedArray<LayoutFunction::const_iterator>(lfs.size()); |
| std::transform(lfs.begin(), lfs.end(), segments.begin(), |
| [](const LayoutFunction& lf) { |
| CHECK(!lf.empty()); |
| return lf.begin(); |
| }); |
| |
| return Choice(&segments); |
| } |
| |
| // Returns LayoutFunction 'lf' with layout indented using 'indent' spaces. |
| LayoutFunction Indent(const LayoutFunction& lf, int indent) const; |
| |
| // Joins layouts horizontally and wraps them into multiple lines to stay under |
| // column limit. Kind of like a words in a paragraph. |
| LayoutFunction Wrap(std::initializer_list<LayoutFunction> lfs, |
| bool use_tokens_break_penalty = false, |
| int hanging_indentation = 0) const { |
| return Wrap(lfs.begin(), lfs.end(), use_tokens_break_penalty, |
| hanging_indentation); |
| } |
| |
| // See Wrap(std::initializer_list<LayoutFunction> lfs). |
| // |
| // Iterator: iterator type that dereferences to LayoutFunction. |
| template <class Iterator> |
| LayoutFunction Wrap(const Iterator begin, const Iterator end, |
| bool use_tokens_break_penalty = false, |
| int hanging_indentation = 0) const { |
| static_assert(IsIteratorDereferencingTo<Iterator, LayoutFunction>, |
| "Iterator's value type must be LayoutFunction."); |
| |
| const auto lfs = make_container_range(begin, end); |
| |
| if (lfs.empty()) return LayoutFunction(); |
| if (lfs.size() == 1) return lfs.front(); |
| |
| absl::FixedArray<LayoutFunction> results(lfs.size()); |
| |
| const int size = lfs.size(); |
| for (int i = size - 1; i >= 0; --i) { |
| absl::FixedArray<LayoutFunction> results_i(size - i); |
| LayoutFunction incremental = lfs[i]; |
| for (int j = i; j < size - 1; ++j) { |
| results_i[j - i] = Stack({ |
| incremental, |
| (i == 0) ? Indent(results[j + 1], hanging_indentation) |
| : results[j + 1], |
| }); |
| |
| const auto& next_element = lfs[j + 1]; |
| if (use_tokens_break_penalty) { |
| // Deprioritize token-level wrapping |
| // TODO(mglb): Find a better way to do this. This ratio has been |
| // chosen using only a few test cases. |
| const int wrapping_penalty = style_.over_column_limit_penalty; |
| const auto& second_layout = results[j + 1].front().layout; |
| const auto& first_line = LeftmostDescendant(second_layout).Value(); |
| const auto& first_token = first_line.TokensRange().front(); |
| const int token_break_penalty = first_token.before.break_penalty; |
| |
| for (auto& segment : results_i[j - i]) |
| segment.intercept += wrapping_penalty + token_break_penalty; |
| } |
| |
| if (next_element.MustWrap()) |
| incremental = Stack({ |
| std::move(incremental), |
| Indent(next_element, hanging_indentation), |
| }); |
| else |
| // TODO(mglb): use Stack for invervals where lfs[j] is multiline (i.e. |
| // has any stack sublayouts) |
| incremental = Juxtaposition({std::move(incremental), next_element}); |
| } |
| results_i.back() = std::move(incremental); |
| |
| // Using reverse range to favor layouts with elements packed in earlier |
| // lines. |
| results[i] = Choice(results_i.rbegin(), results_i.rend()); |
| } |
| return results[0]; |
| } |
| |
| private: |
| LayoutFunction Juxtaposition(const LayoutFunction& left, |
| const LayoutFunction& right) const; |
| |
| LayoutFunction Stack( |
| absl::FixedArray<LayoutFunction::const_iterator>* segments) const; |
| |
| static LayoutFunction Choice( |
| absl::FixedArray<LayoutFunction::const_iterator>* segments); |
| |
| const BasicFormatStyle& style_; |
| }; |
| |
| class TokenPartitionsLayoutOptimizer { |
| public: |
| explicit TokenPartitionsLayoutOptimizer(const BasicFormatStyle& style) |
| : factory_(style) {} |
| |
| TokenPartitionsLayoutOptimizer(const TokenPartitionsLayoutOptimizer&) = |
| delete; |
| TokenPartitionsLayoutOptimizer(TokenPartitionsLayoutOptimizer&&) = delete; |
| TokenPartitionsLayoutOptimizer& operator=( |
| const TokenPartitionsLayoutOptimizer&) = delete; |
| TokenPartitionsLayoutOptimizer& operator=(TokenPartitionsLayoutOptimizer&&) = |
| delete; |
| |
| void Optimize(int indentation, TokenPartitionTree* node) const; |
| |
| LayoutFunction CalculateOptimalLayout(const TokenPartitionTree& node) const; |
| |
| private: |
| const LayoutFunctionFactory factory_; |
| }; |
| |
| class TreeReconstructor { |
| public: |
| explicit TreeReconstructor(int indentation_spaces) |
| : current_indentation_spaces_(indentation_spaces) {} |
| ~TreeReconstructor() = default; |
| |
| TreeReconstructor(const TreeReconstructor&) = delete; |
| TreeReconstructor(TreeReconstructor&&) = delete; |
| TreeReconstructor& operator=(const TreeReconstructor&) = delete; |
| TreeReconstructor& operator=(TreeReconstructor&&) = delete; |
| |
| void TraverseTree(const LayoutTree& layout_tree); |
| |
| void ReplaceTokenPartitionTreeNode(TokenPartitionTree* node); |
| |
| private: |
| TokenPartitionTree tree_; |
| TokenPartitionTree* current_node_ = nullptr; |
| |
| int current_indentation_spaces_; |
| }; |
| |
| } // namespace verible |
| |
| #endif // VERIBLE_VERILOG_FORMATTING_LAYOUT_OPTIMIZER_INTERNAL_H_ |