blob: 8a350679b58545bc5c9d05c6700a464b2bd11964 [file] [log] [blame]
// Copyright 2017-2020 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.
#include "common/formatting/format_token.h"
#include <iostream>
#include <sstream> // pragma IWYU: keep // for ostringstream
#include <string>
#include "absl/base/macros.h"
#include "absl/strings/string_view.h"
#include "common/strings/display_utils.h"
#include "common/strings/range.h"
#include "common/text/token_info.h"
#include "common/util/iterator_adaptors.h"
#include "common/util/logging.h"
#include "common/util/spacer.h"
namespace verible {
std::ostream& operator<<(std::ostream& stream, SpacingOptions b) {
switch (b) {
case SpacingOptions::Undecided:
stream << "undecided";
break;
case SpacingOptions::MustAppend:
stream << "must-append";
break;
case SpacingOptions::MustWrap:
stream << "must-wrap";
break;
case SpacingOptions::AppendAligned:
stream << "append-aligned";
break;
case SpacingOptions::Preserve:
stream << "preserve";
break;
}
return stream;
}
std::ostream& operator<<(std::ostream& stream, GroupBalancing b) {
switch (b) {
case GroupBalancing::None:
stream << "none";
break;
case GroupBalancing::Open:
stream << "open";
break;
case GroupBalancing::Close:
stream << "close";
break;
}
return stream;
}
std::ostream& operator<<(std::ostream& stream, const InterTokenInfo& t) {
stream << "{\n spaces_required: " << t.spaces_required
<< "\n break_penalty: " << t.break_penalty
<< "\n break_decision: " << t.break_decision
<< "\n preserve_space?: " << (t.preserved_space_start != nullptr)
<< "\n}";
return stream;
}
std::ostream& InterTokenInfo::CompactNotation(std::ostream& stream) const {
stream << '<';
// break_penalty is irrelevant when the options are constrained,
// so don't bother showing it in those cases.
switch (break_decision) {
case SpacingOptions::Undecided:
stream << '_' << spaces_required << ',' << break_penalty;
break;
case SpacingOptions::MustAppend:
stream << "+_" << spaces_required;
break;
case SpacingOptions::MustWrap:
// spaces_required is irrelevant
stream << "\\n";
break;
case SpacingOptions::AppendAligned:
stream << "|_" << spaces_required;
break;
case SpacingOptions::Preserve:
stream << "pre";
break;
}
return stream << '>';
}
std::ostream& operator<<(std::ostream& stream, SpacingDecision d) {
switch (d) {
case SpacingDecision::Append:
stream << "append";
break;
case SpacingDecision::Wrap:
stream << "wrap";
break;
case SpacingDecision::Align:
stream << "align";
break;
case SpacingDecision::Preserve:
stream << "preserve";
break;
}
return stream;
}
static SpacingDecision ConvertSpacing(SpacingOptions opt) {
switch (opt) {
case SpacingOptions::MustWrap:
return SpacingDecision::Wrap;
case SpacingOptions::MustAppend:
return SpacingDecision::Append;
case SpacingOptions::AppendAligned:
return SpacingDecision::Align;
default: // Undecided, Preserve
return SpacingDecision::Preserve;
}
}
InterTokenDecision::InterTokenDecision(const InterTokenInfo& info)
: spaces(info.spaces_required),
action(ConvertSpacing(info.break_decision)),
preserved_space_start(info.preserved_space_start) {}
static absl::string_view OriginalLeadingSpacesRange(const char* begin,
const char* end) {
if (begin == nullptr) {
VLOG(4) << "no original space range";
return make_string_view_range(end, end); // empty range
}
// The original spacing points into the original string buffer, and may span
// multiple whitespace tokens.
VLOG(4) << "non-null original space range";
return make_string_view_range(begin, end);
}
absl::string_view FormattedToken::OriginalLeadingSpaces() const {
return OriginalLeadingSpacesRange(before.preserved_space_start,
token->text().begin());
}
std::ostream& FormattedToken::FormattedText(std::ostream& stream) const {
switch (before.action) {
case SpacingDecision::Preserve: {
if (before.preserved_space_start != nullptr) {
// Calculate string_view range of pre-existing spaces, and print that.
stream << OriginalLeadingSpaces();
} else {
// During testing, we are less interested in Preserve mode due to lack
// of "original spacing", so fall-back to safe behavior.
stream << Spacer(before.spaces);
}
break;
}
case SpacingDecision::Wrap:
// Never print spaces before a newline.
stream << '\n';
ABSL_FALLTHROUGH_INTENDED;
case SpacingDecision::Align:
case SpacingDecision::Append:
stream << Spacer(before.spaces);
break;
}
return stream << token->text();
}
std::ostream& operator<<(std::ostream& stream, const FormattedToken& token) {
return token.FormattedText(stream);
}
absl::string_view PreFormatToken::OriginalLeadingSpaces() const {
return OriginalLeadingSpacesRange(before.preserved_space_start,
token->text().begin());
}
size_t PreFormatToken::LeadingSpacesLength() const {
if (before.break_decision == SpacingOptions::Preserve &&
before.preserved_space_start != nullptr) {
return OriginalLeadingSpaces().length();
}
// in other cases (append, wrap), take the spaces_required value.
return before.spaces_required;
}
int PreFormatToken::ExcessSpaces() const {
if (before.preserved_space_start == nullptr) return 0;
const absl::string_view leading_spaces = OriginalLeadingSpaces();
int delta = 0;
if (!absl::StrContains(leading_spaces, "\n")) {
delta = static_cast<int>(leading_spaces.length()) - before.spaces_required;
}
return delta;
}
std::string PreFormatToken::ToString() const {
std::ostringstream output_stream;
output_stream << *this;
return output_stream.str();
}
// Human readable token information
std::ostream& operator<<(std::ostream& stream, const PreFormatToken& t) {
// don't care about byte offsets
return t.token->ToStream(stream << "TokenInfo: ")
<< "\nenum: " << t.format_token_enum << "\nbefore: " << t.before
<< "\nbalance: " << t.balancing << std::endl;
}
void ConnectPreFormatTokensPreservedSpaceStarts(
const char* buffer_start, std::vector<PreFormatToken>* format_tokens) {
VLOG(4) << __FUNCTION__;
CHECK(buffer_start != nullptr);
for (auto& ftoken : *format_tokens) {
ftoken.before.preserved_space_start = buffer_start;
VLOG(4) << "space: " << VisualizeWhitespace(ftoken.OriginalLeadingSpaces());
buffer_start = ftoken.Text().end();
}
// This does not cover the spacing between the last token and EOF.
}
// Finds the span of format tokens covered by the 'byte_offset_range'.
// Run-time: O(lg N) due to binary search
static MutableFormatTokenRange FindFormatTokensInByteOffsetRange(
std::vector<PreFormatToken>::iterator begin,
std::vector<PreFormatToken>::iterator end,
const std::pair<int, int>& byte_offset_range, absl::string_view base_text) {
const auto tokens_begin =
std::lower_bound(begin, end, byte_offset_range.first,
[=](const PreFormatToken& t, int position) {
return t.token->left(base_text) < position;
});
const auto tokens_end =
std::upper_bound(tokens_begin, end, byte_offset_range.second,
[=](int position, const PreFormatToken& t) {
return position < t.token->right(base_text);
});
return {tokens_begin, tokens_end};
}
void PreserveSpacesOnDisabledTokenRanges(
std::vector<PreFormatToken>* ftokens,
const ByteOffsetSet& disabled_byte_ranges, absl::string_view base_text) {
VLOG(2) << __FUNCTION__;
// saved_iter: shrink bounds of binary search with every iteration,
// due to monotonic, non-overlapping intervals.
auto saved_iter = ftokens->begin();
for (const auto& byte_range : disabled_byte_ranges) {
// 'disable_range' marks the range of format tokens to be
// marked as preserving original spacing (i.e. not formatted).
VLOG(2) << "disabling bytes: " << AsInterval(byte_range);
const auto disable_range = FindFormatTokensInByteOffsetRange(
saved_iter, ftokens->end(), byte_range, base_text);
const std::pair<int, int> disabled_token_indices(SubRangeIndices(
disable_range, make_range(ftokens->begin(), ftokens->end())));
VLOG(2) << "disabling tokens: " << AsInterval(disabled_token_indices);
// kludge: When the disabled range immediately follows a //-style
// comment, skip past the trailing '\n' (not included in the comment
// token), which will be printed by the Emit() method, and preserve the
// whitespaces *beyond* that point up to the start of the following
// token's text. This way, rendering the start of the format-disabled
// excerpt won't get redundant '\n's.
if (!disable_range.empty()) {
auto& first = disable_range.front();
VLOG(3) << "checking whether first ftoken in range is a must-wrap.";
if (first.before.break_decision == SpacingOptions::MustWrap) {
VLOG(3) << "checking if spaces before first ftoken starts with \\n.";
const absl::string_view leading_space = first.OriginalLeadingSpaces();
// consume the first '\n' from the preceding inter-token spaces
if (absl::StartsWith(leading_space, "\n")) {
VLOG(3) << "consuming leading \\n.";
++first.before.preserved_space_start;
}
}
}
// Mark tokens in the disabled range as preserving original spaces.
for (auto& ft : disable_range) {
VLOG(2) << "disable-format preserve spaces before: " << *ft.token;
ft.before.break_decision = SpacingOptions::Preserve;
}
// start next iteration search from previous iteration's end
saved_iter = disable_range.end();
}
}
} // namespace verible