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