blob: 39e599dc58db1a9d545b624977279204c2acf790 [file] [log] [blame]
// 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(
&current_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(&current_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