179 lines · cpp
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-exception6//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 15using 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.19static 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 26Annotations::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 83size_t Annotations::point(llvm::StringRef Name) const {84 return pointWithPayload(Name).first;85}86 87std::pair<size_t, llvm::StringRef>88Annotations::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 96std::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 105std::vector<std::pair<size_t, llvm::StringRef>>106Annotations::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 119llvm::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 128Annotations::Range Annotations::range(llvm::StringRef Name) const {129 return rangeWithPayload(Name).first;130}131 132std::pair<Annotations::Range, llvm::StringRef>133Annotations::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 141std::vector<Annotations::Range>142Annotations::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}150std::vector<std::pair<Annotations::Range, llvm::StringRef>>151Annotations::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 165llvm::StringMap<llvm::SmallVector<Annotations::Range, 1>>166Annotations::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 175llvm::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