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, Text); 29 }; 30 std::optional<llvm::StringRef> Name; 31 std::optional<llvm::StringRef> Payload; 32 llvm::SmallVector<Annotation, 8> OpenRanges; 33 34 Code.reserve(Text.size()); 35 while (!Text.empty()) { 36 if (Text.consume_front("^")) { 37 All.push_back( 38 {Code.size(), size_t(-1), Name.value_or(""), Payload.value_or("")}); 39 Points[Name.value_or("")].push_back(All.size() - 1); 40 Name = std::nullopt; 41 Payload = std::nullopt; 42 continue; 43 } 44 if (Text.consume_front("[[")) { 45 OpenRanges.push_back( 46 {Code.size(), size_t(-1), Name.value_or(""), Payload.value_or("")}); 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("]]")) { 53 Require(!OpenRanges.empty(), "unmatched ]]"); 54 55 const Annotation &NewRange = OpenRanges.back(); 56 All.push_back( 57 {NewRange.Begin, Code.size(), NewRange.Name, NewRange.Payload}); 58 Ranges[NewRange.Name].push_back(All.size() - 1); 59 60 OpenRanges.pop_back(); 61 continue; 62 } 63 if (Text.consume_front("$")) { 64 Name = 65 Text.take_while([](char C) { return llvm::isAlnum(C) || C == '_'; }); 66 Text = Text.drop_front(Name->size()); 67 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); 72 } 73 74 continue; 75 } 76 Code.push_back(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(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}; 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(Pts.size()); 100 for (const auto &[Point, Payload] : Pts) 101 Positions.push_back(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(Name); 108 if (Iter == Points.end()) 109 return {}; 110 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}); 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(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}; 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(WithPayload.size()); 146 for (const auto &[Range, Payload] : WithPayload) 147 Res.push_back(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(Name); 153 if (Iter == Ranges.end()) 154 return {}; 155 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}, 160 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("[{0}, {1})", R.Begin, R.End); 178 } 179