| // 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 of a code layout optimizer described by |
| // Phillip Yelland in "A New Approach to Optimal Code Formatting" |
| // (https://research.google/pubs/pub44667/) and originally implemented |
| // in rfmt (https://github.com/google/rfmt). |
| |
| #include "common/formatting/layout_optimizer.h" |
| |
| #include <algorithm> |
| #include <iomanip> |
| #include <ostream> |
| |
| #include "absl/container/fixed_array.h" |
| #include "common/formatting/basic_format_style.h" |
| #include "common/formatting/layout_optimizer_internal.h" |
| #include "common/formatting/line_wrap_searcher.h" |
| #include "common/formatting/token_partition_tree.h" |
| #include "common/formatting/unwrapped_line.h" |
| #include "common/util/container_iterator_range.h" |
| #include "common/util/logging.h" |
| #include "common/util/tree_operations.h" |
| #include "common/util/value_saver.h" |
| |
| namespace verible { |
| |
| void OptimizeTokenPartitionTree(const BasicFormatStyle& style, |
| TokenPartitionTree* node) { |
| CHECK_NOTNULL(node); |
| VLOG(4) << __FUNCTION__ << ", before:\n" |
| << verible::TokenPartitionTreePrinter(*node); |
| |
| const auto optimizer = TokenPartitionsLayoutOptimizer(style); |
| const auto indentation = node->Value().IndentationSpaces(); |
| optimizer.Optimize(indentation, node); |
| |
| VLOG(4) << __FUNCTION__ << ", after:\n" |
| << verible::TokenPartitionTreePrinter(*node); |
| } |
| |
| namespace { |
| |
| // Adopts sublayouts of 'source' into 'destination' if 'source' and |
| // 'destination' types are equal and 'source' doesn't have extra indentation. |
| // Otherwise adopts whole 'source'. |
| void AdoptLayoutAndFlattenIfSameType(const LayoutTree& source, |
| LayoutTree* destination) { |
| CHECK_NOTNULL(destination); |
| const auto& src_item = source.Value(); |
| const auto& dst_item = destination->Value(); |
| if (!verible::is_leaf(source) && src_item.Type() == dst_item.Type() && |
| src_item.IndentationSpaces() == 0) { |
| const auto& first_subitem = source.Children().front().Value(); |
| CHECK(src_item.MustWrap() == first_subitem.MustWrap()); |
| CHECK(src_item.SpacesBefore() == first_subitem.SpacesBefore()); |
| destination->Children().reserve(destination->Children().size() + |
| source.Children().size()); |
| for (const auto& sublayout : source.Children()) |
| AdoptSubtree(*destination, sublayout); |
| } else { |
| AdoptSubtree(*destination, source); |
| } |
| } |
| |
| int AlreadyFormattedPartitionLength(const TokenPartitionTree& partition) { |
| auto tokens = partition.Value().TokensRange(); |
| if (tokens.empty()) return 0; |
| int width = 0; |
| |
| width += partition.Value().IndentationSpaces(); |
| width += tokens.front().Length(); |
| |
| for (const auto& token : make_range(tokens.begin() + 1, tokens.end())) { |
| // TODO(mglb): either handle tokens with kPreserve break_decision, or |
| // explicitly check for their absence. Preserved space is currently expected |
| // to be emulated with kAlreadyFormatted/kInline partitions. Only tabular |
| // aligner creates such partitions. |
| width += token.before.spaces_required + token.Length(); |
| } |
| |
| for (const auto& child : partition.Children()) { |
| CHECK_EQ(child.Value().PartitionPolicy(), PartitionPolicyEnum::kInline); |
| if (child.Value().TokensRange().begin() != tokens.begin()) { |
| const auto& first_token = child.Value().TokensRange().front(); |
| // Substract spacing added in the loop above |
| width -= first_token.before.spaces_required; |
| } |
| width += child.Value().IndentationSpaces(); |
| } |
| |
| return width; |
| } |
| |
| // Largest possible column value, used as infinity. |
| constexpr int kInfinity = std::numeric_limits<int>::max(); |
| |
| } // namespace |
| |
| std::ostream& operator<<(std::ostream& stream, LayoutType type) { |
| switch (type) { |
| case LayoutType::kLine: |
| return stream << "line"; |
| case LayoutType::kJuxtaposition: |
| return stream << "juxtaposition"; |
| case LayoutType::kStack: |
| return stream << "stack"; |
| } |
| LOG(WARNING) << "Unknown layout type: " << int(type); |
| return stream << "???"; |
| } |
| |
| std::ostream& operator<<(std::ostream& stream, const LayoutItem& layout) { |
| if (layout.Type() == LayoutType::kLine) { |
| stream << "[ " << layout.Text() << " ]" |
| << ", length: " << layout.Length(); |
| } else { |
| stream << "[<" << layout.Type() << ">]"; |
| } |
| stream << ", indentation: " << layout.IndentationSpaces() |
| << ", spacing: " << layout.SpacesBefore() |
| << ", must wrap: " << (layout.MustWrap() ? "YES" : "no"); |
| return stream; |
| } |
| |
| std::ostream& operator<<(std::ostream& stream, |
| const LayoutFunctionSegment& segment) { |
| stream << "[" << std::setw(3) << std::setfill(' ') << segment.column << "] (" |
| << std::fixed << std::setprecision(3) << segment.intercept << " + " |
| << segment.gradient << "*x), span: " << segment.span << ", layout:\n"; |
| PrintTree(segment.layout, &stream, 6); |
| return stream; |
| } |
| |
| std::ostream& operator<<(std::ostream& stream, const LayoutFunction& lf) { |
| if (lf.empty()) return stream << "{}"; |
| |
| stream << "{\n"; |
| for (const auto& segment : lf) { |
| stream << " [" << std::setw(3) << std::setfill(' ') << segment.column |
| << "] (" << std::fixed << std::setprecision(3) << std::setw(8) |
| << std::setfill(' ') << segment.intercept << " + " << std::setw(4) |
| << std::setfill(' ') << segment.gradient |
| << "*x), span: " << std::setw(3) << std::setfill(' ') << segment.span |
| << ", layout:\n"; |
| PrintTree(segment.layout, &stream, 8); |
| stream << "\n"; |
| } |
| stream << "}"; |
| return stream; |
| } |
| |
| LayoutFunction::iterator LayoutFunction::begin() { |
| return LayoutFunction::iterator(*this, 0); |
| } |
| |
| LayoutFunction::iterator LayoutFunction::end() { |
| return LayoutFunction::iterator(*this, size()); |
| } |
| |
| LayoutFunction::const_iterator LayoutFunction::begin() const { |
| return LayoutFunction::const_iterator(*this, 0); |
| } |
| |
| LayoutFunction::const_iterator LayoutFunction::end() const { |
| return LayoutFunction::const_iterator(*this, size()); |
| } |
| |
| LayoutFunction::const_iterator LayoutFunction::AtOrToTheLeftOf( |
| int column) const { |
| if (empty()) return end(); |
| |
| const auto it = std::lower_bound( |
| begin(), end(), column, [](const LayoutFunctionSegment& s, int column) { |
| return s.column <= column; |
| }); |
| CHECK_NE(it, begin()); |
| return it - 1; |
| } |
| |
| LayoutFunction LayoutFunctionFactory::WrappedLine( |
| const UnwrappedLine& uwline) const { |
| const auto tokens = uwline.TokensRange(); |
| auto token_lfs = absl::FixedArray<LayoutFunction>(tokens.size()); |
| |
| auto lf = token_lfs.begin(); |
| for (auto t = tokens.begin(); t != tokens.end(); ++t) { |
| auto token_line = UnwrappedLine(0, t); |
| token_line.SpanNextToken(); |
| *lf = Line(token_line); |
| ++lf; |
| } |
| return Wrap(token_lfs.begin(), token_lfs.end(), true, style_.wrap_spaces); |
| } |
| |
| LayoutFunction LayoutFunctionFactory::Line(const UnwrappedLine& uwline) const { |
| auto layout = LayoutTree(LayoutItem(uwline)); |
| const auto span = layout.Value().Length(); |
| |
| if (span < style_.column_limit) { |
| return LayoutFunction{ |
| // 0 <= X < column_limit-span |
| {0, layout, span, 0, 0}, |
| // column_limit-span <= X |
| {style_.column_limit - span, std::move(layout), span, 0, |
| style_.over_column_limit_penalty}, |
| }; |
| } else { |
| return LayoutFunction{ |
| {0, std::move(layout), span, |
| float((span - style_.column_limit) * style_.over_column_limit_penalty), |
| style_.over_column_limit_penalty}, |
| }; |
| } |
| } |
| |
| LayoutFunction LayoutFunctionFactory::Indent(const LayoutFunction& lf, |
| int indent) const { |
| CHECK(!lf.empty()); |
| CHECK_GE(indent, 0); |
| |
| LayoutFunction result; |
| |
| auto indent_column = 0; |
| auto column = indent; |
| auto segment = lf.AtOrToTheLeftOf(column); |
| |
| while (true) { |
| auto columns_over_limit = column - style_.column_limit; |
| |
| const float new_intercept = |
| segment->CostAt(column) - |
| style_.over_column_limit_penalty * std::max(columns_over_limit, 0); |
| const int new_gradient = segment->gradient; |
| |
| auto new_layout = segment->layout; |
| new_layout.Value().SetIndentationSpaces( |
| new_layout.Value().IndentationSpaces() + indent); |
| |
| const int new_span = indent + segment->span; |
| |
| result.push_back(LayoutFunctionSegment{indent_column, std::move(new_layout), |
| new_span, new_intercept, |
| new_gradient}); |
| |
| ++segment; |
| if (segment == lf.end()) break; |
| column = segment->column; |
| indent_column = column - indent; |
| } |
| |
| return result; |
| } |
| |
| LayoutFunction LayoutFunctionFactory::Juxtaposition( |
| const LayoutFunction& left, const LayoutFunction& right) const { |
| CHECK(!left.empty()); |
| CHECK(!right.empty()); |
| |
| if (right.MustWrap()) { |
| // TODO(mglb): print code fragment that caused this. |
| LOG(ERROR) << "Tried to juxtapose partition that must wrap." |
| << "\n*** Please file a bug. ***"; |
| // Do not crash, fallback to stack layout. Set huge penalty to let the |
| // layout be dropped in Choice() operator if it ends up in one. |
| auto result = Stack({left, right}); |
| for (auto& segment : result) segment.intercept += 2e6; |
| return result; |
| } |
| |
| LayoutFunction result; |
| |
| auto segment_l = left.begin(); |
| auto segment_r = right.begin(); |
| |
| auto column_l = 0; |
| auto column_r = segment_l->span + segment_r->layout.Value().SpacesBefore(); |
| segment_r = right.AtOrToTheLeftOf(column_r); |
| |
| while (true) { |
| const int columns_over_limit = column_r - style_.column_limit; |
| |
| const float new_intercept = |
| segment_l->CostAt(column_l) + segment_r->CostAt(column_r) - |
| style_.over_column_limit_penalty * std::max(columns_over_limit, 0); |
| const int new_gradient = |
| segment_l->gradient + segment_r->gradient - |
| (columns_over_limit >= 0 ? style_.over_column_limit_penalty : 0); |
| |
| const auto& layout_l = segment_l->layout; |
| const auto& layout_r = segment_r->layout; |
| auto new_layout = LayoutTree(LayoutItem(LayoutType::kJuxtaposition, |
| layout_l.Value().SpacesBefore(), |
| layout_l.Value().MustWrap())); |
| |
| AdoptLayoutAndFlattenIfSameType(layout_l, &new_layout); |
| AdoptLayoutAndFlattenIfSameType(layout_r, &new_layout); |
| |
| const int new_span = |
| segment_l->span + segment_r->span + layout_r.Value().SpacesBefore(); |
| |
| result.push_back(LayoutFunctionSegment{column_l, std::move(new_layout), |
| new_span, new_intercept, |
| new_gradient}); |
| |
| auto next_segment_l = segment_l + 1; |
| auto next_column_l = kInfinity; |
| if (next_segment_l != left.end()) next_column_l = next_segment_l->column; |
| |
| auto next_segment_r = segment_r + 1; |
| auto next_column_r = kInfinity; |
| if (next_segment_r != right.end()) next_column_r = next_segment_r->column; |
| |
| if (next_segment_l == left.end() && next_segment_r == right.end()) break; |
| |
| if (next_segment_r == right.end() || |
| (next_column_l - column_l) <= (next_column_r - column_r)) { |
| column_l = next_column_l; |
| column_r = next_column_l + next_segment_l->span + |
| layout_r.Value().SpacesBefore(); |
| |
| segment_l = next_segment_l; |
| segment_r = right.AtOrToTheLeftOf(column_r); |
| } else { |
| column_r = next_column_r; |
| column_l = |
| next_column_r - segment_l->span - layout_r.Value().SpacesBefore(); |
| |
| segment_r = next_segment_r; |
| } |
| } |
| |
| return result; |
| } |
| |
| LayoutFunction LayoutFunctionFactory::Stack( |
| absl::FixedArray<LayoutFunction::const_iterator>* segments) const { |
| CHECK(!segments->empty()); |
| |
| LayoutFunction result; |
| |
| const float line_breaks_penalty = |
| (segments->size() - 1) * style_.line_break_penalty; |
| |
| // Iterate over columns from left to right and process a segment of each |
| // LayoutFunction that is under currently iterated column. |
| int current_column = 0; |
| do { |
| // Point iterators to segments under current column. |
| for (auto& segment_it : *segments) { |
| segment_it.MoveToKnotAtOrToTheLeftOf(current_column); |
| } |
| |
| // Use first line's spacing for new layouts. |
| const auto& first_layout_item = segments->front()->layout.Value(); |
| const auto spaces_before = first_layout_item.SpacesBefore(); |
| const auto break_decision = first_layout_item.MustWrap(); |
| // Use last line's span for new layouts. Other lines won't be modified by |
| // any further layout combinations. |
| const int span = segments->back()->span; |
| |
| auto new_segment = LayoutFunctionSegment{ |
| current_column, |
| LayoutTree( |
| LayoutItem(LayoutType::kStack, spaces_before, break_decision)), |
| span, line_breaks_penalty, 0}; |
| |
| for (const auto& segment_it : *segments) { |
| new_segment.intercept += segment_it->CostAt(current_column); |
| new_segment.gradient += segment_it->gradient; |
| AdoptLayoutAndFlattenIfSameType(segment_it->layout, &new_segment.layout); |
| } |
| result.push_back(std::move(new_segment)); |
| |
| { |
| // Find next column. |
| int next_column = kInfinity; |
| for (auto segment_it : *segments) { |
| if ((segment_it + 1).IsEnd()) continue; |
| const int column = (segment_it + 1)->column; |
| CHECK_GE(column, current_column); |
| if (column < next_column) next_column = column; |
| } |
| current_column = next_column; |
| } |
| } while (current_column < kInfinity); |
| |
| return result; |
| } |
| |
| LayoutFunction LayoutFunctionFactory::Choice( |
| absl::FixedArray<LayoutFunction::const_iterator>* segments) { |
| CHECK(!segments->empty()); |
| |
| LayoutFunction result; |
| |
| // Initial value set to an iterator that doesn't point to any existing |
| // segment. |
| LayoutFunction::const_iterator last_min_cost_segment = |
| segments->front().Container().end(); |
| |
| int current_column = 0; |
| // Iterate (in increasing order) over starting columns (knots) of all |
| // segments of every LayoutFunction. |
| do { |
| // Starting column of the next closest segment. |
| int next_knot = kInfinity; |
| |
| for (auto& segment_it : *segments) { |
| segment_it.MoveToKnotAtOrToTheLeftOf(current_column); |
| |
| const int column = |
| (segment_it + 1).IsEnd() ? kInfinity : segment_it[1].column; |
| if (column < next_knot) next_knot = column; |
| } |
| |
| do { |
| const LayoutFunction::const_iterator min_cost_segment = *std::min_element( |
| segments->begin(), segments->end(), |
| [current_column](const auto& a, const auto& b) { |
| if (a->CostAt(current_column) != b->CostAt(current_column)) |
| return (a->CostAt(current_column) < b->CostAt(current_column)); |
| // Sort by gradient when cost is the same. Favor earlier |
| // element when both gradients are equal. |
| return (a->gradient < b->gradient); |
| }); |
| |
| if (min_cost_segment != last_min_cost_segment) { |
| result.push_back(LayoutFunctionSegment{ |
| current_column, min_cost_segment->layout, min_cost_segment->span, |
| min_cost_segment->CostAt(current_column), |
| min_cost_segment->gradient}); |
| last_min_cost_segment = min_cost_segment; |
| } |
| |
| // Find closest crossover point located before next knot. |
| int next_column = next_knot; |
| for (const auto& segment : *segments) { |
| if (segment->gradient >= min_cost_segment->gradient) continue; |
| float gamma = (segment->CostAt(current_column) - |
| min_cost_segment->CostAt(current_column)) / |
| (min_cost_segment->gradient - segment->gradient); |
| int column = current_column + std::ceil(gamma); |
| if (column > current_column && column < next_column) { |
| next_column = column; |
| } |
| } |
| |
| current_column = next_column; |
| } while (current_column < next_knot); |
| } while (current_column < kInfinity); |
| |
| return result; |
| } |
| |
| void TokenPartitionsLayoutOptimizer::Optimize(int indentation, |
| TokenPartitionTree* node) const { |
| CHECK_NOTNULL(node); |
| CHECK_GE(indentation, 0); |
| |
| const LayoutFunction layout_function = CalculateOptimalLayout(*node); |
| |
| CHECK(!layout_function.empty()); |
| VLOG(4) << __FUNCTION__ << ", layout function:\n" << layout_function; |
| |
| auto iter = layout_function.AtOrToTheLeftOf(indentation); |
| CHECK(iter != layout_function.end()); |
| VLOG(4) << __FUNCTION__ << ", layout:\n" << iter->layout; |
| |
| TreeReconstructor tree_reconstructor(indentation); |
| tree_reconstructor.TraverseTree(iter->layout); |
| tree_reconstructor.ReplaceTokenPartitionTreeNode(node); |
| } |
| |
| LayoutFunction TokenPartitionsLayoutOptimizer::CalculateOptimalLayout( |
| const TokenPartitionTree& node) const { |
| if (is_leaf(node)) { |
| // Wrapping complexity is n*(n+1)/2. |
| constexpr int kWrapTokensLimit = 25; |
| |
| if (node.Value().PartitionPolicy() == PartitionPolicyEnum::kWrap && |
| node.Value().TokensRange().size() > 1 && |
| node.Value().TokensRange().size() < kWrapTokensLimit) { |
| return factory_.WrappedLine(node.Value()); |
| } else { |
| return factory_.Line(node.Value()); |
| } |
| } |
| |
| // Traverse and calculate children layouts |
| |
| absl::FixedArray<LayoutFunction> layouts(node.Children().size()); |
| |
| switch (node.Value().PartitionPolicy()) { |
| case PartitionPolicyEnum::kJuxtaposition: |
| case PartitionPolicyEnum::kAlreadyFormatted: |
| case PartitionPolicyEnum::kWrap: |
| case PartitionPolicyEnum::kFitOnLineElseExpand: |
| case PartitionPolicyEnum::kAppendFittingSubPartitions: |
| case PartitionPolicyEnum::kJuxtapositionOrIndentedStack: { |
| std::transform(node.Children().begin(), node.Children().end(), |
| layouts.begin(), [this](const TokenPartitionTree& n) { |
| return this->CalculateOptimalLayout(n); |
| }); |
| break; |
| } |
| |
| case PartitionPolicyEnum::kStack: |
| case PartitionPolicyEnum::kAlwaysExpand: |
| case PartitionPolicyEnum::kTabularAlignment: { |
| const int indentation = node.Value().IndentationSpaces(); |
| std::transform( |
| node.Children().begin(), node.Children().end(), layouts.begin(), |
| [this, &node, indentation](const TokenPartitionTree& n) { |
| const int relative_indentation = |
| n.Value().IndentationSpaces() - indentation; |
| if (relative_indentation < 0) { |
| VLOG(0) << "(Child indentation) < (parent indentation). " |
| "Assuming 0. Parent node:\n" |
| << node; |
| } |
| |
| LayoutFunction lf; |
| |
| if (relative_indentation > 0) { |
| lf = factory_.Indent(this->CalculateOptimalLayout(n), |
| relative_indentation); |
| } else { |
| lf = this->CalculateOptimalLayout(n); |
| } |
| return lf; |
| }); |
| break; |
| } |
| |
| case PartitionPolicyEnum::kUninitialized: |
| case PartitionPolicyEnum::kInline: |
| // Shouldn't happen - see the switch below. |
| break; |
| } |
| |
| // Calculate and return current layout |
| |
| switch (node.Value().PartitionPolicy()) { |
| case PartitionPolicyEnum::kJuxtaposition: |
| return factory_.Juxtaposition(layouts.begin(), layouts.end()); |
| case PartitionPolicyEnum::kStack: |
| return factory_.Stack(layouts.begin(), layouts.end()); |
| case PartitionPolicyEnum::kWrap: { |
| if (VLOG_IS_ON(0) && node.Children().size() > 2) { |
| const int indentation = node.Children()[1].Value().IndentationSpaces(); |
| for (const auto& child : iterator_range(node.Children().begin() + 2, |
| node.Children().end())) { |
| if (child.Value().IndentationSpaces() != indentation) { |
| VLOG(0) << "Indentations of subpartitions from the second to the " |
| "last are not equal. Using indentation of the second " |
| "subpartition as a hanging indentation. Parent node:\n" |
| << node; |
| } |
| } |
| } |
| // std::max(hanging_indentation, 0) serves to resolve a case where |
| // the subsequent nodes will try to be indented below zero following |
| // a less-indented token eg. a macro |
| const int hanging_indentation = |
| (node.Children().size() > 1) |
| ? std::max(node.Children()[1].Value().IndentationSpaces() - |
| node.Value().IndentationSpaces(), |
| 0) |
| : 0; |
| |
| return factory_.Wrap(layouts.begin(), layouts.end(), false, |
| hanging_indentation); |
| } |
| |
| case PartitionPolicyEnum::kJuxtapositionOrIndentedStack: { |
| LayoutFunction juxtaposition; |
| const bool juxtaposition_allowed = |
| !std::any_of(layouts.begin() + 1, layouts.end(), |
| [](const LayoutFunction& lf) { return lf.MustWrap(); }); |
| if (juxtaposition_allowed) { |
| juxtaposition = factory_.Juxtaposition(layouts.begin(), layouts.end()); |
| } |
| |
| const int indentation = node.Value().IndentationSpaces(); |
| for (size_t i = 0; i < layouts.size(); ++i) { |
| const int relative_indentation = |
| node.Children()[i].Value().IndentationSpaces() - indentation; |
| layouts[i] = factory_.Indent(layouts[i], relative_indentation); |
| } |
| auto stack = factory_.Stack(layouts.begin(), layouts.end()); |
| |
| if (juxtaposition_allowed) |
| return factory_.Choice({std::move(juxtaposition), std::move(stack)}); |
| return stack; |
| } |
| |
| case PartitionPolicyEnum::kInline: { |
| // Shouldn't happen - the partition with this policy should always |
| // be a leaf. Anyway, try to handle it without aborting. |
| LOG(ERROR) << "Partition node with kInline policy should be " |
| "a leaf. Dropping its children. Partition node:\n" |
| << node << "\n\n*** Please file a bug. ***"; |
| return factory_.Line(node.Value()); |
| } |
| |
| case PartitionPolicyEnum::kAlreadyFormatted: { |
| // When not a leaf, it contains partitions with kInline |
| // policy. Pack them horizontally. |
| const bool all_children_are_inlines = |
| std::all_of(node.Children().begin(), node.Children().end(), |
| [](const TokenPartitionTree& child) { |
| return child.Value().PartitionPolicy() == |
| PartitionPolicyEnum::kInline; |
| }); |
| LOG_IF(ERROR, !all_children_are_inlines) |
| << "Partition node with kAlreadyFormatted policy should not " |
| "contain children with policies other than kInline. " |
| "Partition node:\n" |
| << node << "\n\n*** Please file a bug. ***"; |
| |
| layouts.front().SetMustWrap(true); |
| |
| // Preserve spacing of the first sublayout. This has to be done because |
| // the first layout in a line uses IndentationSpaces instead of |
| // SpacesBefore. |
| const auto indent = node.Children().front().Value().IndentationSpaces(); |
| layouts.front() = factory_.Indent(layouts.front(), indent); |
| |
| return factory_.Juxtaposition(layouts.begin(), layouts.end()); |
| } |
| |
| case PartitionPolicyEnum::kAppendFittingSubPartitions: |
| case PartitionPolicyEnum::kFitOnLineElseExpand: |
| return factory_.Wrap(layouts.begin(), layouts.end()); |
| |
| case PartitionPolicyEnum::kAlwaysExpand: |
| case PartitionPolicyEnum::kTabularAlignment: |
| return factory_.Stack(layouts.begin(), layouts.end()); |
| |
| case PartitionPolicyEnum::kUninitialized: |
| break; |
| } |
| |
| // Stack layout is probably syntax-safe in all situations. Try it without |
| // aborting. |
| LOG(ERROR) << "Unsupported partition policy: " |
| << node.Value().PartitionPolicy() |
| << ". Defaulting to stack layout. Partition node:\n" |
| << node << "\n\n*** Please file a bug. ***"; |
| return factory_.Stack(layouts.begin(), layouts.end()); |
| } |
| |
| void TreeReconstructor::TraverseTree(const LayoutTree& layout_tree) { |
| const auto& layout = layout_tree.Value(); |
| const auto relative_indentation = layout.IndentationSpaces(); |
| const ValueSaver<int> indent_saver( |
| ¤t_indentation_spaces_, |
| current_indentation_spaces_ + relative_indentation); |
| // Setting indentation for a line that is going to be appended is invalid and |
| // probably has been done for some reason that is not going to work as |
| // intended. |
| LOG_IF(WARNING, ((relative_indentation > 0) && (current_node_ != nullptr))) |
| << "Discarding indentation of a line that's going to be appended."; |
| |
| switch (layout.Type()) { |
| case LayoutType::kLine: { |
| CHECK(layout_tree.Children().empty()); |
| |
| if (current_node_ == nullptr) { |
| auto uwline = UnwrappedLine(current_indentation_spaces_, |
| layout.TokensRange().begin(), |
| PartitionPolicyEnum::kAlreadyFormatted); |
| uwline.SpanUpToToken(layout.TokensRange().end()); |
| tree_.Children().emplace_back(uwline); |
| current_node_ = &tree_.Children().back(); |
| } else { |
| const auto tokens = layout.TokensRange(); |
| CHECK(current_node_->Value().TokensRange().end() == tokens.begin()); |
| |
| current_node_->Value().SpanUpToToken(tokens.end()); |
| |
| auto& slices = current_node_->Children(); |
| // TODO(mglb): add support for break_decision == Preserve |
| if (layout.SpacesBefore() == tokens.front().before.spaces_required) { |
| // No need for separate inline partition |
| if (!slices.empty()) |
| slices.back().Value().SpanUpToToken(tokens.end()); |
| return; |
| } |
| |
| // Wrap previous tokens in the line |
| if (slices.empty()) { |
| current_node_->Children().emplace_back( |
| UnwrappedLine(0, current_node_->Value().TokensRange().begin(), |
| PartitionPolicyEnum::kInline)); |
| } |
| slices.back().Value().SpanUpToToken(tokens.begin()); |
| |
| // Wrap tokens from current layout |
| auto slice = UnwrappedLine(layout.SpacesBefore(), tokens.begin(), |
| PartitionPolicyEnum::kInline); |
| slice.SpanUpToToken(tokens.end()); |
| current_node_->Children().emplace_back(slice); |
| } |
| return; |
| } |
| |
| case LayoutType::kJuxtaposition: { |
| // Append all children |
| for (const auto& child : layout_tree.Children()) { |
| TraverseTree(child); |
| } |
| return; |
| } |
| |
| case LayoutType::kStack: { |
| if (layout_tree.Children().empty()) { |
| return; |
| } |
| if (layout_tree.Children().size() == 1) { |
| TraverseTree(layout_tree.Children().front()); |
| return; |
| } |
| |
| // Calculate indent for 2nd and further lines. |
| int indentation = current_indentation_spaces_; |
| if (current_node_ != nullptr) { |
| indentation = AlreadyFormattedPartitionLength(*current_node_) + |
| layout.SpacesBefore(); |
| } |
| |
| // Append first child |
| TraverseTree(layout_tree.Children().front()); |
| |
| // Put remaining children in their own (indented) lines |
| const ValueSaver<int> indent_saver(¤t_indentation_spaces_, |
| indentation); |
| for (const auto& child : make_range(layout_tree.Children().begin() + 1, |
| layout_tree.Children().end())) { |
| current_node_ = nullptr; |
| TraverseTree(child); |
| } |
| return; |
| } |
| } |
| } |
| |
| void TreeReconstructor::ReplaceTokenPartitionTreeNode( |
| TokenPartitionTree* node) { |
| CHECK_NOTNULL(node); |
| CHECK(!tree_.Children().empty()); |
| |
| if (tree_.Children().size() == 1) { |
| *node = std::move(tree_.Children().front()); |
| } else { |
| const auto& first_line = tree_.Children().front().Value(); |
| const auto& last_line = tree_.Children().back().Value(); |
| |
| node->Value() = UnwrappedLine(current_indentation_spaces_, |
| first_line.TokensRange().begin(), |
| PartitionPolicyEnum::kAlwaysExpand); |
| node->Value().SpanUpToToken(last_line.TokensRange().end()); |
| node->Children().clear(); |
| AdoptSubtreesFrom(*node, &tree_); |
| } |
| } |
| |
| } // namespace verible |