blob: 046295c456eed2c214ff236239d8f46a5e5305b4 [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 "verilog/formatting/comment_controls.h"
#include <iostream>
#include <vector>
#include "absl/strings/match.h"
#include "absl/strings/str_split.h"
#include "absl/strings/strip.h"
#include "common/strings/comment_utils.h"
#include "common/strings/display_utils.h"
#include "common/strings/line_column_map.h"
#include "common/util/logging.h"
#include "common/util/range.h"
#include "common/util/spacer.h"
#include "verilog/parser/verilog_parser.h"
#include "verilog/parser/verilog_token_classifications.h"
#include "verilog/parser/verilog_token_enum.h"
namespace verilog {
namespace formatter {
using verible::ByteOffsetSet;
using verible::VisualizeWhitespace;
ByteOffsetSet DisableFormattingRanges(absl::string_view text,
const verible::TokenSequence& tokens) {
static constexpr absl::string_view kTrigger = "verilog_format:";
static const auto kDelimiters = absl::ByAnyChar(" \t");
static constexpr int kNullOffset = -1;
const verible::TokenInfo::Context context(
text,
[](std::ostream& stream, int e) { stream << verilog_symbol_name(e); });
// By default, no text ranges are formatter-disabled.
int begin_disable_offset = kNullOffset;
ByteOffsetSet disable_set;
for (const auto& token : tokens) {
VLOG(2) << verible::TokenWithContext{token, context};
const auto vtoken_enum = verilog_tokentype(token.token_enum());
if (IsComment(vtoken_enum)) {
// Focus on the space-delimited tokens in the comment text.
auto commands = verible::StripCommentAndSpacePadding(token.text());
if (absl::ConsumePrefix(&commands, kTrigger)) {
const std::vector<absl::string_view> comment_tokens(
absl::StrSplit(commands, kDelimiters, absl::SkipEmpty()));
if (!comment_tokens.empty()) {
// "off" marks the start of a disabling range, at end of comment.
// "on" marks the end of disabling range, up to the end of comment.
if (comment_tokens.front() == "off") {
if (begin_disable_offset == kNullOffset) {
begin_disable_offset = token.right(text);
if (vtoken_enum == TK_EOL_COMMENT) {
++begin_disable_offset; // to cover the trailing '\n'
}
} // else ignore
} else if (comment_tokens.front() == "on") {
if (begin_disable_offset != kNullOffset) {
const int end_disable_offset = token.right(text);
if (begin_disable_offset != end_disable_offset) {
disable_set.Add({begin_disable_offset, end_disable_offset});
}
begin_disable_offset = kNullOffset;
} // else ignore
}
}
}
}
}
// If the disabling interval remains open, close it (to end-of-buffer).
if (begin_disable_offset != kNullOffset) {
disable_set.Add({begin_disable_offset, static_cast<int>(text.length())});
}
return disable_set;
}
ByteOffsetSet EnabledLinesToDisabledByteRanges(
const verible::LineNumberSet& line_numbers,
const verible::LineColumnMap& line_column_map) {
// Interpret empty line numbers as enabling all lines for formatting.
if (line_numbers.empty()) return ByteOffsetSet();
// Translate lines to byte offsets (strictly monotonic).
const int max_line = line_column_map.GetBeginningOfLineOffsets().size() + 1;
ByteOffsetSet byte_offsets(
line_numbers.MonotonicTransform<int>([&](int line_number) {
// line_numbers are 1-based, while OffsetAtLine is 0-based
const int n = std::max(std::min(line_number, max_line), 1);
return line_column_map.OffsetAtLine(n - 1);
}));
// Invert set to get disabled ranges.
const int end_byte = line_column_map.EndOffset();
byte_offsets.Complement({0, end_byte});
return byte_offsets;
}
static size_t NewlineCount(absl::string_view s) {
return std::count(s.begin(), s.end(), '\n');
}
void FormatWhitespaceWithDisabledByteRanges(
absl::string_view text_base, absl::string_view space_text,
const ByteOffsetSet& disabled_ranges, std::ostream& stream) {
VLOG(3) << __FUNCTION__;
CHECK(verible::IsSubRange(space_text, text_base));
const int start = std::distance(text_base.begin(), space_text.begin());
const int end = start + space_text.length();
ByteOffsetSet enabled_ranges{{start, end}}; // initial interval set mask
enabled_ranges.Difference(disabled_ranges);
VLOG(3) << "space range: [" << start << ", " << end << ')';
VLOG(3) << "disabled ranges: " << disabled_ranges;
VLOG(3) << "enabled ranges: " << enabled_ranges;
// Special case if space_text is empty.
if (space_text.empty() && start != 0) {
if (!disabled_ranges.Contains(start)) {
stream << '\n';
return;
}
}
// Traverse alternating disabled and enabled ranges.
bool partially_enabled = false;
size_t total_enabled_newlines = 0;
int next_start = start; // keep track of last consumed position
for (const auto& range : enabled_ranges) {
{ // for disabled intervals, print the original spacing
const absl::string_view disabled(
text_base.substr(next_start, range.first - next_start));
VLOG(3) << "preserved: \"" << VisualizeWhitespace{disabled} << '"';
stream << disabled;
total_enabled_newlines += NewlineCount(disabled);
}
{ // for enabled intervals, preserve only newlines
const absl::string_view enabled(
text_base.substr(range.first, range.second - range.first));
const size_t newline_count = NewlineCount(enabled);
VLOG(3) << "formatted: " << newline_count << "*\\n";
stream << verible::Spacer(newline_count, '\n');
partially_enabled = true;
total_enabled_newlines += newline_count;
}
next_start = range.second;
}
// If there is a disabled interval left over, print that.
const absl::string_view final_disabled(
text_base.substr(next_start, end - next_start));
stream << final_disabled;
total_enabled_newlines += NewlineCount(final_disabled);
// Print at least one newline if some subrange was format-enabled.
if (partially_enabled && total_enabled_newlines == 0 && start != 0) {
stream << '\n';
}
}
} // namespace formatter
} // namespace verilog