aboutsummaryrefslogtreecommitdiff
path: root/backend/lib/Support/TextDiagnostic.cpp
blob: 1c027c30c94ff3142e8623462e48359bd4477083 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
// SPDX-License-Identifier: GPL-3.0-or-later

#include "rcc/Support/TextDiagnostic.h"

#include <algorithm>
#include <iterator>
#include <sstream>
#include <utility>

namespace rcc {

std::string_view getDiagnosticSeverityName(DiagnosticSeverity Severity) noexcept {
  switch (Severity) {
  case DiagnosticSeverity::Note:
    return "note";
  case DiagnosticSeverity::Warning:
    return "warning";
  case DiagnosticSeverity::Error:
    return "error";
  }

  return "unknown";
}

TextSource::TextSource(std::string Name, std::string Text)
    : Name(std::move(Name)), Text(std::move(Text)), LineOffsets{0} {
  for (std::size_t Index = 0; Index < this->Text.size(); ++Index) {
    if (this->Text[Index] == '\n')
      LineOffsets.push_back(Index + 1);
  }
}

TextPosition TextSource::positionAt(std::size_t Offset) const noexcept {
  Offset = std::min(Offset, Text.size());
  const auto LineIterator = std::upper_bound(LineOffsets.begin(), LineOffsets.end(), Offset);
  const std::size_t LineIndex =
      static_cast<std::size_t>(std::distance(LineOffsets.begin(), LineIterator) - 1);
  return TextPosition{
      .Offset = Offset,
      .Line = LineIndex + 1,
      .Column = Offset - LineOffsets[LineIndex] + 1,
  };
}

TextRange TextSource::range(std::size_t Begin, std::size_t End) const noexcept {
  Begin = std::min(Begin, Text.size());
  End = std::clamp(End, Begin, Text.size());
  return TextRange{.Begin = positionAt(Begin), .End = positionAt(End)};
}

std::string_view TextSource::line(std::size_t OneBasedLine) const noexcept {
  if (OneBasedLine == 0 || OneBasedLine > LineOffsets.size())
    return {};

  const std::size_t Begin = LineOffsets[OneBasedLine - 1];
  std::size_t End = OneBasedLine < LineOffsets.size() ? LineOffsets[OneBasedLine] - 1 : Text.size();
  if (End > Begin && Text[End - 1] == '\r')
    --End;
  return std::string_view(Text).substr(Begin, End - Begin);
}

std::string formatTextDiagnostic(const TextDiagnostic &Diagnostic, const TextSource &Source) {
  std::ostringstream Output;
  const TextPosition Position = Diagnostic.Range.Begin;
  const std::string_view SourceName =
      Diagnostic.SourceName.empty() ? Source.name() : std::string_view(Diagnostic.SourceName);

  Output << SourceName << ':' << Position.Line << ':' << Position.Column << ": "
         << getDiagnosticSeverityName(Diagnostic.Severity);
  if (!Diagnostic.Category.empty())
    Output << '[' << Diagnostic.Category << ']';
  Output << ": " << Diagnostic.Message;

  const std::string_view SourceLine = Source.line(Position.Line);
  if (SourceLine.empty())
    return Output.str();

  Output << '\n' << SourceLine << '\n';
  const std::size_t PrefixWidth = Position.Column > 0 ? Position.Column - 1 : 0;
  for (std::size_t Index = 0; Index < PrefixWidth && Index < SourceLine.size(); ++Index)
    Output << (SourceLine[Index] == '\t' ? '\t' : ' ');

  Output << '^';
  if (Diagnostic.Range.End.Line == Position.Line &&
      Diagnostic.Range.End.Column > Position.Column + 1) {
    Output << std::string(Diagnostic.Range.End.Column - Position.Column - 1, '~');
  }
  return Output.str();
}

} // namespace rcc