| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 "./base_doc_printer.h" |
| |
| namespace tvm { |
| namespace script { |
| namespace printer { |
| |
| namespace { |
| |
| std::vector<ByteSpan> MergeAndExemptSpans(const std::vector<ByteSpan>& spans, |
| const std::vector<ByteSpan>& spans_exempted) { |
| // use prefix sum to merge and exempt spans |
| std::vector<ByteSpan> res; |
| std::vector<std::pair<size_t, int>> prefix_stamp; |
| for (ByteSpan span : spans) { |
| prefix_stamp.push_back({span.first, 1}); |
| prefix_stamp.push_back({span.second, -1}); |
| } |
| // at most spans.size() spans accumulated in prefix sum |
| // use spans.size() + 1 as stamp unit to exempt all positive spans |
| // with only one negative span |
| int max_n = spans.size() + 1; |
| for (ByteSpan span : spans_exempted) { |
| prefix_stamp.push_back({span.first, -max_n}); |
| prefix_stamp.push_back({span.second, max_n}); |
| } |
| std::sort(prefix_stamp.begin(), prefix_stamp.end()); |
| int prefix_sum = 0; |
| int n = prefix_stamp.size(); |
| for (int i = 0; i < n - 1; ++i) { |
| prefix_sum += prefix_stamp[i].second; |
| // positive prefix sum leads to spans without exemption |
| // different stamp positions guarantee the stamps in same position accumulated |
| if (prefix_sum > 0 && prefix_stamp[i].first < prefix_stamp[i + 1].first) { |
| if (res.size() && res.back().second == prefix_stamp[i].first) { |
| // merge to the last spans if it is successive |
| res.back().second = prefix_stamp[i + 1].first; |
| } else { |
| // add a new independent span |
| res.push_back({prefix_stamp[i].first, prefix_stamp[i + 1].first}); |
| } |
| } |
| } |
| return res; |
| } |
| |
| size_t GetTextWidth(const std::string& text, const ByteSpan& span) { |
| // FIXME: this only works for ASCII characters. |
| // To do this "correctly", we need to parse UTF-8 into codepoints |
| // and call wcwidth() or equivalent for every codepoint. |
| size_t ret = 0; |
| for (size_t i = span.first; i != span.second; ++i) { |
| if (isprint(text[i])) { |
| ret += 1; |
| } |
| } |
| return ret; |
| } |
| |
| size_t MoveBack(size_t pos, size_t distance) { return distance > pos ? 0 : pos - distance; } |
| |
| size_t MoveForward(size_t pos, size_t distance, size_t max) { |
| return distance > max - pos ? max : pos + distance; |
| } |
| |
| size_t GetLineIndex(size_t byte_pos, const std::vector<size_t>& line_starts) { |
| auto it = std::upper_bound(line_starts.begin(), line_starts.end(), byte_pos); |
| return (it - line_starts.begin()) - 1; |
| } |
| |
| using UnderlineIter = typename std::vector<ByteSpan>::const_iterator; |
| |
| ByteSpan PopNextUnderline(UnderlineIter* next_underline, UnderlineIter end_underline) { |
| if (*next_underline == end_underline) { |
| return {std::numeric_limits<size_t>::max(), std::numeric_limits<size_t>::max()}; |
| } else { |
| return *(*next_underline)++; |
| } |
| } |
| |
| void PrintChunk(const std::pair<size_t, size_t>& lines_range, |
| const std::pair<UnderlineIter, UnderlineIter>& underlines, const std::string& text, |
| const std::vector<size_t>& line_starts, const PrinterConfig& options, |
| size_t line_number_width, std::string* out) { |
| UnderlineIter next_underline = underlines.first; |
| ByteSpan current_underline = PopNextUnderline(&next_underline, underlines.second); |
| |
| for (size_t line_idx = lines_range.first; line_idx < lines_range.second; ++line_idx) { |
| if (options->print_line_numbers) { |
| std::string line_num_str = std::to_string(line_idx + 1); |
| line_num_str.push_back(' '); |
| for (size_t i = line_num_str.size(); i < line_number_width; ++i) { |
| out->push_back(' '); |
| } |
| *out += line_num_str; |
| } |
| |
| size_t line_start = line_starts.at(line_idx); |
| size_t line_end = |
| line_idx + 1 == line_starts.size() ? text.size() : line_starts.at(line_idx + 1); |
| out->append(text.begin() + line_start, text.begin() + line_end); |
| |
| bool printed_underline = false; |
| size_t line_pos = line_start; |
| bool printed_extra_caret = 0; |
| while (current_underline.first < line_end) { |
| if (!printed_underline) { |
| *out += std::string(line_number_width, ' '); |
| printed_underline = true; |
| } |
| |
| size_t underline_end_for_line = std::min(line_end, current_underline.second); |
| size_t num_spaces = GetTextWidth(text, {line_pos, current_underline.first}); |
| if (num_spaces > 0 && printed_extra_caret) { |
| num_spaces -= 1; |
| printed_extra_caret = false; |
| } |
| *out += std::string(num_spaces, ' '); |
| |
| size_t num_carets = GetTextWidth(text, {current_underline.first, underline_end_for_line}); |
| if (num_carets == 0 && !printed_extra_caret) { |
| // Special case: when underlineing an empty or unprintable string, make sure to print |
| // at least one caret still. |
| num_carets = 1; |
| printed_extra_caret = true; |
| } else if (num_carets > 0 && printed_extra_caret) { |
| num_carets -= 1; |
| printed_extra_caret = false; |
| } |
| *out += std::string(num_carets, '^'); |
| |
| line_pos = current_underline.first = underline_end_for_line; |
| if (current_underline.first == current_underline.second) { |
| current_underline = PopNextUnderline(&next_underline, underlines.second); |
| } |
| } |
| |
| if (printed_underline) { |
| out->push_back('\n'); |
| } |
| } |
| } |
| |
| void PrintCut(size_t num_lines_skipped, std::string* out) { |
| if (num_lines_skipped != 0) { |
| std::ostringstream s; |
| s << "(... " << num_lines_skipped << " lines skipped ...)\n"; |
| *out += s.str(); |
| } |
| } |
| |
| std::pair<size_t, size_t> GetLinesForUnderline(const ByteSpan& underline, |
| const std::vector<size_t>& line_starts, |
| size_t num_lines, const PrinterConfig& options) { |
| size_t first_line_of_underline = GetLineIndex(underline.first, line_starts); |
| size_t first_line_of_chunk = MoveBack(first_line_of_underline, options->num_context_lines); |
| size_t end_line_of_underline = GetLineIndex(underline.second - 1, line_starts) + 1; |
| size_t end_line_of_chunk = |
| MoveForward(end_line_of_underline, options->num_context_lines, num_lines); |
| |
| return {first_line_of_chunk, end_line_of_chunk}; |
| } |
| |
| // If there is only one line between the chunks, it is better to print it as is, |
| // rather than something like "(... 1 line skipped ...)". |
| constexpr const size_t kMinLinesToCutOut = 2; |
| |
| bool TryMergeChunks(std::pair<size_t, size_t>* cur_chunk, |
| const std::pair<size_t, size_t>& new_chunk) { |
| if (new_chunk.first < cur_chunk->second + kMinLinesToCutOut) { |
| cur_chunk->second = new_chunk.second; |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| size_t GetNumLines(const std::string& text, const std::vector<size_t>& line_starts) { |
| if (line_starts.back() == text.size()) { |
| // Final empty line doesn't count as a line |
| return line_starts.size() - 1; |
| } else { |
| return line_starts.size(); |
| } |
| } |
| |
| size_t GetLineNumberWidth(size_t num_lines, const PrinterConfig& options) { |
| if (options->print_line_numbers) { |
| return std::to_string(num_lines).size() + 1; |
| } else { |
| return 0; |
| } |
| } |
| |
| std::string DecorateText(const std::string& text, const std::vector<size_t>& line_starts, |
| const PrinterConfig& options, const std::vector<ByteSpan>& underlines) { |
| size_t num_lines = GetNumLines(text, line_starts); |
| size_t line_number_width = GetLineNumberWidth(num_lines, options); |
| |
| std::string ret; |
| if (underlines.empty()) { |
| PrintChunk({0, num_lines}, {underlines.begin(), underlines.begin()}, text, line_starts, options, |
| line_number_width, &ret); |
| return ret; |
| } |
| |
| size_t last_end_line = 0; |
| std::pair<size_t, size_t> cur_chunk = |
| GetLinesForUnderline(underlines[0], line_starts, num_lines, options); |
| if (cur_chunk.first < kMinLinesToCutOut) { |
| cur_chunk.first = 0; |
| } |
| |
| auto first_underline_in_cur_chunk = underlines.begin(); |
| for (auto underline_it = underlines.begin() + 1; underline_it != underlines.end(); |
| ++underline_it) { |
| std::pair<size_t, size_t> new_chunk = |
| GetLinesForUnderline(*underline_it, line_starts, num_lines, options); |
| |
| if (!TryMergeChunks(&cur_chunk, new_chunk)) { |
| PrintCut(cur_chunk.first - last_end_line, &ret); |
| PrintChunk(cur_chunk, {first_underline_in_cur_chunk, underline_it}, text, line_starts, |
| options, line_number_width, &ret); |
| last_end_line = cur_chunk.second; |
| cur_chunk = new_chunk; |
| first_underline_in_cur_chunk = underline_it; |
| } |
| } |
| |
| PrintCut(cur_chunk.first - last_end_line, &ret); |
| if (num_lines - cur_chunk.second < kMinLinesToCutOut) { |
| cur_chunk.second = num_lines; |
| } |
| PrintChunk(cur_chunk, {first_underline_in_cur_chunk, underlines.end()}, text, line_starts, |
| options, line_number_width, &ret); |
| PrintCut(num_lines - cur_chunk.second, &ret); |
| return ret; |
| } |
| |
| } // namespace |
| |
| DocPrinter::DocPrinter(const PrinterConfig& options) : options_(options) { |
| line_starts_.push_back(0); |
| } |
| |
| void DocPrinter::Append(const Doc& doc) { Append(doc, PrinterConfig()); } |
| |
| void DocPrinter::Append(const Doc& doc, const PrinterConfig& cfg) { |
| for (const AccessPath& p : cfg->path_to_underline) { |
| path_to_underline_.push_back(p); |
| current_max_path_depth_.push_back(0); |
| current_underline_candidates_.push_back(std::vector<ByteSpan>()); |
| } |
| PrintDoc(doc); |
| for (const auto& c : current_underline_candidates_) { |
| underlines_.insert(underlines_.end(), c.begin(), c.end()); |
| } |
| } |
| |
| ffi::String DocPrinter::GetString() const { |
| std::string text = output_.str(); |
| |
| // Remove any trailing indentation |
| while (!text.empty() && text.back() == ' ') { |
| text.pop_back(); |
| } |
| |
| if (!text.empty() && text.back() != '\n') { |
| text.push_back('\n'); |
| } |
| |
| return DecorateText(text, line_starts_, options_, |
| MergeAndExemptSpans(underlines_, underlines_exempted_)); |
| } |
| |
| void DocPrinter::PrintDoc(const Doc& doc) { |
| size_t start_pos = output_.tellp(); |
| |
| if (auto doc_node = doc.as<LiteralDoc>()) { |
| PrintTypedDoc(doc_node.value()); |
| } else if (auto doc_node = doc.as<IdDoc>()) { |
| PrintTypedDoc(doc_node.value()); |
| } else if (auto doc_node = doc.as<AttrAccessDoc>()) { |
| PrintTypedDoc(doc_node.value()); |
| } else if (auto doc_node = doc.as<IndexDoc>()) { |
| PrintTypedDoc(doc_node.value()); |
| } else if (auto doc_node = doc.as<OperationDoc>()) { |
| PrintTypedDoc(doc_node.value()); |
| } else if (auto doc_node = doc.as<CallDoc>()) { |
| PrintTypedDoc(doc_node.value()); |
| } else if (auto doc_node = doc.as<LambdaDoc>()) { |
| PrintTypedDoc(doc_node.value()); |
| } else if (auto doc_node = doc.as<ListDoc>()) { |
| PrintTypedDoc(doc_node.value()); |
| } else if (auto doc_node = doc.as<TupleDoc>()) { |
| PrintTypedDoc(doc_node.value()); |
| } else if (auto doc_node = doc.as<DictDoc>()) { |
| PrintTypedDoc(doc_node.value()); |
| } else if (auto doc_node = doc.as<SliceDoc>()) { |
| PrintTypedDoc(doc_node.value()); |
| } else if (auto doc_node = doc.as<StmtBlockDoc>()) { |
| PrintTypedDoc(doc_node.value()); |
| } else if (auto doc_node = doc.as<AssignDoc>()) { |
| PrintTypedDoc(doc_node.value()); |
| } else if (auto doc_node = doc.as<IfDoc>()) { |
| PrintTypedDoc(doc_node.value()); |
| } else if (auto doc_node = doc.as<WhileDoc>()) { |
| PrintTypedDoc(doc_node.value()); |
| } else if (auto doc_node = doc.as<ForDoc>()) { |
| PrintTypedDoc(doc_node.value()); |
| } else if (auto doc_node = doc.as<ScopeDoc>()) { |
| PrintTypedDoc(doc_node.value()); |
| } else if (auto doc_node = doc.as<ExprStmtDoc>()) { |
| PrintTypedDoc(doc_node.value()); |
| } else if (auto doc_node = doc.as<AssertDoc>()) { |
| PrintTypedDoc(doc_node.value()); |
| } else if (auto doc_node = doc.as<ReturnDoc>()) { |
| PrintTypedDoc(doc_node.value()); |
| } else if (auto doc_node = doc.as<FunctionDoc>()) { |
| PrintTypedDoc(doc_node.value()); |
| } else if (auto doc_node = doc.as<ClassDoc>()) { |
| PrintTypedDoc(doc_node.value()); |
| } else if (auto doc_node = doc.as<CommentDoc>()) { |
| PrintTypedDoc(doc_node.value()); |
| } else if (auto doc_node = doc.as<DocStringDoc>()) { |
| PrintTypedDoc(doc_node.value()); |
| } else { |
| LOG(FATAL) << "Do not know how to print " << doc->GetTypeKey(); |
| throw; |
| } |
| |
| size_t end_pos = output_.tellp(); |
| for (const AccessPath& path : doc->source_paths) { |
| MarkSpan({start_pos, end_pos}, path); |
| } |
| } |
| |
| void DocPrinter::MarkSpan(const ByteSpan& span, const AccessPath& path) { |
| int n = path_to_underline_.size(); |
| for (int i = 0; i < n; ++i) { |
| AccessPath p = path_to_underline_[i]; |
| if (path->depth >= current_max_path_depth_[i] && path->IsPrefixOf(p)) { |
| if (path->depth > current_max_path_depth_[i]) { |
| current_max_path_depth_[i] = path->depth; |
| current_underline_candidates_[i].clear(); |
| } |
| current_underline_candidates_[i].push_back(span); |
| } |
| } |
| } |
| |
| } // namespace printer |
| } // namespace script |
| } // namespace tvm |