1 | //===--- Annotations.cpp - Annotated source code for unit tests --*- C++-*-===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "llvm/Testing/Annotations/Annotations.h" |
10 | |
11 | #include "llvm/ADT/StringExtras.h" |
12 | #include "llvm/Support/FormatVariadic.h" |
13 | #include "llvm/Support/raw_ostream.h" |
14 | |
15 | using namespace llvm; |
16 | |
17 | // Crash if the assertion fails, printing the message and testcase. |
18 | // More elegant error handling isn't needed for unit tests. |
19 | static void require(bool Assertion, const char *Msg, llvm::StringRef Code) { |
20 | if (!Assertion) { |
21 | llvm::errs() << "Annotated testcase: " << Msg << "\n" << Code << "\n" ; |
22 | llvm_unreachable("Annotated testcase assertion failed!" ); |
23 | } |
24 | } |
25 | |
26 | Annotations::Annotations(llvm::StringRef Text) { |
27 | auto Require = [Text](bool Assertion, const char *Msg) { |
28 | require(Assertion, Msg, Code: Text); |
29 | }; |
30 | std::optional<llvm::StringRef> Name; |
31 | std::optional<llvm::StringRef> Payload; |
32 | llvm::SmallVector<Annotation, 8> OpenRanges; |
33 | |
34 | Code.reserve(res: Text.size()); |
35 | while (!Text.empty()) { |
36 | if (Text.consume_front(Prefix: "^" )) { |
37 | All.push_back( |
38 | x: {.Begin: Code.size(), .End: size_t(-1), .Name: Name.value_or(u: "" ), .Payload: Payload.value_or(u: "" )}); |
39 | Points[Name.value_or(u: "" )].push_back(Elt: All.size() - 1); |
40 | Name = std::nullopt; |
41 | Payload = std::nullopt; |
42 | continue; |
43 | } |
44 | if (Text.consume_front(Prefix: "[[" )) { |
45 | OpenRanges.push_back( |
46 | Elt: {.Begin: Code.size(), .End: size_t(-1), .Name: Name.value_or(u: "" ), .Payload: Payload.value_or(u: "" )}); |
47 | Name = std::nullopt; |
48 | Payload = std::nullopt; |
49 | continue; |
50 | } |
51 | Require(!Name, "$name should be followed by ^ or [[" ); |
52 | if (Text.consume_front(Prefix: "]]" )) { |
53 | Require(!OpenRanges.empty(), "unmatched ]]" ); |
54 | |
55 | const Annotation &NewRange = OpenRanges.back(); |
56 | All.push_back( |
57 | x: {.Begin: NewRange.Begin, .End: Code.size(), .Name: NewRange.Name, .Payload: NewRange.Payload}); |
58 | Ranges[NewRange.Name].push_back(Elt: All.size() - 1); |
59 | |
60 | OpenRanges.pop_back(); |
61 | continue; |
62 | } |
63 | if (Text.consume_front(Prefix: "$" )) { |
64 | Name = |
65 | Text.take_while(F: [](char C) { return llvm::isAlnum(C) || C == '_'; }); |
66 | Text = Text.drop_front(N: Name->size()); |
67 | |
68 | if (Text.consume_front(Prefix: "(" )) { |
69 | Payload = Text.take_while(F: [](char C) { return C != ')'; }); |
70 | Require(Text.size() > Payload->size(), "unterminated payload" ); |
71 | Text = Text.drop_front(N: Payload->size() + 1); |
72 | } |
73 | |
74 | continue; |
75 | } |
76 | Code.push_back(c: Text.front()); |
77 | Text = Text.drop_front(); |
78 | } |
79 | Require(!Name, "unterminated $name" ); |
80 | Require(OpenRanges.empty(), "unmatched [[" ); |
81 | } |
82 | |
83 | size_t Annotations::point(llvm::StringRef Name) const { |
84 | return pointWithPayload(Name).first; |
85 | } |
86 | |
87 | std::pair<size_t, llvm::StringRef> |
88 | Annotations::pointWithPayload(llvm::StringRef Name) const { |
89 | auto I = Points.find(Key: Name); |
90 | require(Assertion: I != Points.end() && I->getValue().size() == 1, |
91 | Msg: "expected exactly one point" , Code); |
92 | const Annotation &P = All[I->getValue()[0]]; |
93 | return {P.Begin, P.Payload}; |
94 | } |
95 | |
96 | std::vector<size_t> Annotations::points(llvm::StringRef Name) const { |
97 | auto Pts = pointsWithPayload(Name); |
98 | std::vector<size_t> Positions; |
99 | Positions.reserve(n: Pts.size()); |
100 | for (const auto &[Point, Payload] : Pts) |
101 | Positions.push_back(x: Point); |
102 | return Positions; |
103 | } |
104 | |
105 | std::vector<std::pair<size_t, llvm::StringRef>> |
106 | Annotations::pointsWithPayload(llvm::StringRef Name) const { |
107 | auto Iter = Points.find(Key: Name); |
108 | if (Iter == Points.end()) |
109 | return {}; |
110 | |
111 | std::vector<std::pair<size_t, llvm::StringRef>> Res; |
112 | Res.reserve(n: Iter->getValue().size()); |
113 | for (size_t I : Iter->getValue()) |
114 | Res.push_back(x: {All[I].Begin, All[I].Payload}); |
115 | |
116 | return Res; |
117 | } |
118 | |
119 | llvm::StringMap<llvm::SmallVector<size_t, 1>> Annotations::all_points() const { |
120 | llvm::StringMap<llvm::SmallVector<size_t, 1>> Result; |
121 | for (const auto &Name : Points.keys()) { |
122 | auto Pts = points(Name); |
123 | Result[Name] = {Pts.begin(), Pts.end()}; |
124 | } |
125 | return Result; |
126 | } |
127 | |
128 | Annotations::Range Annotations::range(llvm::StringRef Name) const { |
129 | return rangeWithPayload(Name).first; |
130 | } |
131 | |
132 | std::pair<Annotations::Range, llvm::StringRef> |
133 | Annotations::rangeWithPayload(llvm::StringRef Name) const { |
134 | auto I = Ranges.find(Key: Name); |
135 | require(Assertion: I != Ranges.end() && I->getValue().size() == 1, |
136 | Msg: "expected exactly one range" , Code); |
137 | const Annotation &R = All[I->getValue()[0]]; |
138 | return {{.Begin: R.Begin, .End: R.End}, R.Payload}; |
139 | } |
140 | |
141 | std::vector<Annotations::Range> |
142 | Annotations::ranges(llvm::StringRef Name) const { |
143 | auto WithPayload = rangesWithPayload(Name); |
144 | std::vector<Annotations::Range> Res; |
145 | Res.reserve(n: WithPayload.size()); |
146 | for (const auto &[Range, Payload] : WithPayload) |
147 | Res.push_back(x: Range); |
148 | return Res; |
149 | } |
150 | std::vector<std::pair<Annotations::Range, llvm::StringRef>> |
151 | Annotations::rangesWithPayload(llvm::StringRef Name) const { |
152 | auto Iter = Ranges.find(Key: Name); |
153 | if (Iter == Ranges.end()) |
154 | return {}; |
155 | |
156 | std::vector<std::pair<Annotations::Range, llvm::StringRef>> Res; |
157 | Res.reserve(n: Iter->getValue().size()); |
158 | for (size_t I : Iter->getValue()) |
159 | Res.emplace_back(args: Annotations::Range{.Begin: All[I].Begin, .End: All[I].End}, |
160 | args: All[I].Payload); |
161 | |
162 | return Res; |
163 | } |
164 | |
165 | llvm::StringMap<llvm::SmallVector<Annotations::Range, 1>> |
166 | Annotations::all_ranges() const { |
167 | llvm::StringMap<llvm::SmallVector<Annotations::Range, 1>> Res; |
168 | for (const llvm::StringRef &Name : Ranges.keys()) { |
169 | auto R = ranges(Name); |
170 | Res[Name] = {R.begin(), R.end()}; |
171 | } |
172 | return Res; |
173 | } |
174 | |
175 | llvm::raw_ostream &llvm::operator<<(llvm::raw_ostream &O, |
176 | const llvm::Annotations::Range &R) { |
177 | return O << llvm::formatv(Fmt: "[{0}, {1})" , Vals: R.Begin, Vals: R.End); |
178 | } |
179 | |