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