1 //===--- Annotations.cpp - Annotated source code for unit tests --*- C++-*-===//
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
7 //===----------------------------------------------------------------------===//
9 #include "llvm/Testing/Annotations/Annotations.h"
11 #include "llvm/ADT/StringExtras.h"
12 #include "llvm/Support/FormatVariadic.h"
13 #include "llvm/Support/raw_ostream.h"
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
) {
21 llvm::errs() << "Annotated testcase: " << Msg
<< "\n" << Code
<< "\n";
22 llvm_unreachable("Annotated testcase assertion failed!");
26 Annotations::Annotations(llvm::StringRef Text
) {
27 auto Require
= [Text
](bool Assertion
, const char *Msg
) {
28 require(Assertion
, Msg
, Text
);
30 std::optional
<llvm::StringRef
> Name
;
31 std::optional
<llvm::StringRef
> Payload
;
32 llvm::SmallVector
<Annotation
, 8> OpenRanges
;
34 Code
.reserve(Text
.size());
35 while (!Text
.empty()) {
36 if (Text
.consume_front("^")) {
38 {Code
.size(), size_t(-1), Name
.value_or(""), Payload
.value_or("")});
39 Points
[Name
.value_or("")].push_back(All
.size() - 1);
41 Payload
= std::nullopt
;
44 if (Text
.consume_front("[[")) {
46 {Code
.size(), size_t(-1), Name
.value_or(""), Payload
.value_or("")});
48 Payload
= std::nullopt
;
51 Require(!Name
, "$name should be followed by ^ or [[");
52 if (Text
.consume_front("]]")) {
53 Require(!OpenRanges
.empty(), "unmatched ]]");
55 const Annotation
&NewRange
= OpenRanges
.back();
57 {NewRange
.Begin
, Code
.size(), NewRange
.Name
, NewRange
.Payload
});
58 Ranges
[NewRange
.Name
].push_back(All
.size() - 1);
60 OpenRanges
.pop_back();
63 if (Text
.consume_front("$")) {
65 Text
.take_while([](char C
) { return llvm::isAlnum(C
) || C
== '_'; });
66 Text
= Text
.drop_front(Name
->size());
68 if (Text
.consume_front("(")) {
69 Payload
= Text
.take_while([](char C
) { return C
!= ')'; });
70 Require(Text
.size() > Payload
->size(), "unterminated payload");
71 Text
= Text
.drop_front(Payload
->size() + 1);
76 Code
.push_back(Text
.front());
77 Text
= Text
.drop_front();
79 Require(!Name
, "unterminated $name");
80 Require(OpenRanges
.empty(), "unmatched [[");
83 size_t Annotations::point(llvm::StringRef Name
) const {
84 return pointWithPayload(Name
).first
;
87 std::pair
<size_t, llvm::StringRef
>
88 Annotations::pointWithPayload(llvm::StringRef Name
) const {
89 auto I
= Points
.find(Name
);
90 require(I
!= Points
.end() && I
->getValue().size() == 1,
91 "expected exactly one point", Code
);
92 const Annotation
&P
= All
[I
->getValue()[0]];
93 return {P
.Begin
, P
.Payload
};
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(Pts
.size());
100 for (const auto &[Point
, Payload
] : Pts
)
101 Positions
.push_back(Point
);
105 std::vector
<std::pair
<size_t, llvm::StringRef
>>
106 Annotations::pointsWithPayload(llvm::StringRef Name
) const {
107 auto Iter
= Points
.find(Name
);
108 if (Iter
== Points
.end())
111 std::vector
<std::pair
<size_t, llvm::StringRef
>> Res
;
112 Res
.reserve(Iter
->getValue().size());
113 for (size_t I
: Iter
->getValue())
114 Res
.push_back({All
[I
].Begin
, All
[I
].Payload
});
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()};
128 Annotations::Range
Annotations::range(llvm::StringRef Name
) const {
129 return rangeWithPayload(Name
).first
;
132 std::pair
<Annotations::Range
, llvm::StringRef
>
133 Annotations::rangeWithPayload(llvm::StringRef Name
) const {
134 auto I
= Ranges
.find(Name
);
135 require(I
!= Ranges
.end() && I
->getValue().size() == 1,
136 "expected exactly one range", Code
);
137 const Annotation
&R
= All
[I
->getValue()[0]];
138 return {{R
.Begin
, R
.End
}, R
.Payload
};
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(WithPayload
.size());
146 for (const auto &[Range
, Payload
] : WithPayload
)
147 Res
.push_back(Range
);
150 std::vector
<std::pair
<Annotations::Range
, llvm::StringRef
>>
151 Annotations::rangesWithPayload(llvm::StringRef Name
) const {
152 auto Iter
= Ranges
.find(Name
);
153 if (Iter
== Ranges
.end())
156 std::vector
<std::pair
<Annotations::Range
, llvm::StringRef
>> Res
;
157 Res
.reserve(Iter
->getValue().size());
158 for (size_t I
: Iter
->getValue())
159 Res
.emplace_back(Annotations::Range
{All
[I
].Begin
, All
[I
].End
},
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()};
175 llvm::raw_ostream
&llvm::operator<<(llvm::raw_ostream
&O
,
176 const llvm::Annotations::Range
&R
) {
177 return O
<< llvm::formatv("[{0}, {1})", R
.Begin
, R
.End
);