| // 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_ |