1//===--- StringSwitch.h - Switch-on-literal-string Construct --------------===/
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/// \file
9/// This file implements the StringSwitch template, which mimics a switch()
10/// statement whose cases are string literals.
11///
12//===----------------------------------------------------------------------===/
13#ifndef LLVM_ADT_STRINGSWITCH_H
14#define LLVM_ADT_STRINGSWITCH_H
15
16#include "llvm/ADT/StringRef.h"
17#include "llvm/Support/ErrorHandling.h"
18#include <cassert>
19#include <cstring>
20#include <initializer_list>
21#include <optional>
22
23namespace llvm {
24
25/// A switch()-like statement whose cases are string literals.
26///
27/// The StringSwitch class is a simple form of a switch() statement that
28/// determines whether the given string matches one of the given string
29/// literals. The template type parameter \p T is the type of the value that
30/// will be returned from the string-switch expression. For example,
31/// the following code switches on the name of a color in \c argv[i]:
32///
33/// \code
34/// Color color = StringSwitch<Color>(argv[i])
35/// .Case("red", Red)
36/// .Case("orange", Orange)
37/// .Case("yellow", Yellow)
38/// .Case("green", Green)
39/// .Case("blue", Blue)
40/// .Case("indigo", Indigo)
41/// .Cases({"violet", "purple"}, Violet)
42/// .Default(UnknownColor);
43/// \endcode
44///
45/// When multiple matches are found, the value of the first match is returned.
46template<typename T, typename R = T>
47class StringSwitch {
48 /// The string we are matching.
49 const StringRef Str;
50
51 /// The pointer to the result of this switch statement, once known,
52 /// null before that.
53 std::optional<T> Result;
54
55public:
56 explicit StringSwitch(StringRef S)
57 : Str(S), Result() { }
58
59 StringSwitch(StringSwitch &&) = default;
60
61 // StringSwitch is not copyable.
62 StringSwitch(const StringSwitch &) = delete;
63
64 // StringSwitch is not assignable due to 'Str' being 'const'.
65 void operator=(const StringSwitch &) = delete;
66 void operator=(StringSwitch &&) = delete;
67
68 // Case-sensitive case matchers.
69 StringSwitch &Case(StringLiteral S, T Value) {
70 CaseImpl(S, Value);
71 return *this;
72 }
73
74 StringSwitch& EndsWith(StringLiteral S, T Value) {
75 if (!Result && Str.ends_with(Suffix: S)) {
76 Result = std::move(Value);
77 }
78 return *this;
79 }
80
81 StringSwitch& StartsWith(StringLiteral S, T Value) {
82 if (!Result && Str.starts_with(Prefix: S)) {
83 Result = std::move(Value);
84 }
85 return *this;
86 }
87
88 StringSwitch &Cases(std::initializer_list<StringLiteral> CaseStrings,
89 T Value) {
90 return CasesImpl(Cases: CaseStrings, Value);
91 }
92
93 [[deprecated("Pass cases in std::initializer_list instead")]]
94 StringSwitch &Cases(StringLiteral S0, StringLiteral S1, T Value) {
95 return CasesImpl(Cases: {S0, S1}, Value);
96 }
97
98 [[deprecated("Pass cases in std::initializer_list instead")]]
99 StringSwitch &Cases(StringLiteral S0, StringLiteral S1, StringLiteral S2,
100 T Value) {
101 return CasesImpl(Cases: {S0, S1, S2}, Value);
102 }
103
104 [[deprecated("Pass cases in std::initializer_list instead")]]
105 StringSwitch &Cases(StringLiteral S0, StringLiteral S1, StringLiteral S2,
106 StringLiteral S3, T Value) {
107 return CasesImpl(Cases: {S0, S1, S2, S3}, Value);
108 }
109
110 [[deprecated("Pass cases in std::initializer_list instead")]]
111 StringSwitch &Cases(StringLiteral S0, StringLiteral S1, StringLiteral S2,
112 StringLiteral S3, StringLiteral S4, T Value) {
113 return CasesImpl(Cases: {S0, S1, S2, S3, S4}, Value);
114 }
115
116 [[deprecated("Pass cases in std::initializer_list instead")]]
117 StringSwitch &Cases(StringLiteral S0, StringLiteral S1, StringLiteral S2,
118 StringLiteral S3, StringLiteral S4, StringLiteral S5,
119 T Value) {
120 return CasesImpl(Cases: {S0, S1, S2, S3, S4, S5}, Value);
121 }
122
123 [[deprecated("Pass cases in std::initializer_list instead")]]
124 StringSwitch &Cases(StringLiteral S0, StringLiteral S1, StringLiteral S2,
125 StringLiteral S3, StringLiteral S4, StringLiteral S5,
126 StringLiteral S6, T Value) {
127 return CasesImpl(Cases: {S0, S1, S2, S3, S4, S5, S6}, Value);
128 }
129
130 [[deprecated("Pass cases in std::initializer_list instead")]]
131 StringSwitch &Cases(StringLiteral S0, StringLiteral S1, StringLiteral S2,
132 StringLiteral S3, StringLiteral S4, StringLiteral S5,
133 StringLiteral S6, StringLiteral S7, T Value) {
134 return CasesImpl(Cases: {S0, S1, S2, S3, S4, S5, S6, S7}, Value);
135 }
136
137 [[deprecated("Pass cases in std::initializer_list instead")]]
138 StringSwitch &Cases(StringLiteral S0, StringLiteral S1, StringLiteral S2,
139 StringLiteral S3, StringLiteral S4, StringLiteral S5,
140 StringLiteral S6, StringLiteral S7, StringLiteral S8,
141 T Value) {
142 return CasesImpl(Cases: {S0, S1, S2, S3, S4, S5, S6, S7, S8}, Value);
143 }
144
145 [[deprecated("Pass cases in std::initializer_list instead")]]
146 StringSwitch &Cases(StringLiteral S0, StringLiteral S1, StringLiteral S2,
147 StringLiteral S3, StringLiteral S4, StringLiteral S5,
148 StringLiteral S6, StringLiteral S7, StringLiteral S8,
149 StringLiteral S9, T Value) {
150 return CasesImpl(Cases: {S0, S1, S2, S3, S4, S5, S6, S7, S8, S9}, Value);
151 }
152
153 // Case-insensitive case matchers.
154 StringSwitch &CaseLower(StringLiteral S, T Value) {
155 CaseLowerImpl(S, Value);
156 return *this;
157 }
158
159 StringSwitch &EndsWithLower(StringLiteral S, T Value) {
160 if (!Result && Str.ends_with_insensitive(Suffix: S))
161 Result = std::move(Value);
162
163 return *this;
164 }
165
166 StringSwitch &StartsWithLower(StringLiteral S, T Value) {
167 if (!Result && Str.starts_with_insensitive(Prefix: S))
168 Result = std::move(Value);
169
170 return *this;
171 }
172
173 StringSwitch &CasesLower(std::initializer_list<StringLiteral> CaseStrings,
174 T Value) {
175 return CasesLowerImpl(Cases: CaseStrings, Value);
176 }
177
178 [[deprecated("Pass cases in std::initializer_list instead")]]
179 StringSwitch &CasesLower(StringLiteral S0, StringLiteral S1, T Value) {
180 return CasesLowerImpl(Cases: {S0, S1}, Value);
181 }
182
183 [[deprecated("Pass cases in std::initializer_list instead")]]
184 StringSwitch &CasesLower(StringLiteral S0, StringLiteral S1, StringLiteral S2,
185 T Value) {
186 return CasesLowerImpl(Cases: {S0, S1, S2}, Value);
187 }
188
189 [[deprecated("Pass cases in std::initializer_list instead")]]
190 StringSwitch &CasesLower(StringLiteral S0, StringLiteral S1, StringLiteral S2,
191 StringLiteral S3, T Value) {
192 return CasesLowerImpl(Cases: {S0, S1, S2, S3}, Value);
193 }
194
195 [[deprecated("Pass cases in std::initializer_list instead")]]
196 StringSwitch &CasesLower(StringLiteral S0, StringLiteral S1, StringLiteral S2,
197 StringLiteral S3, StringLiteral S4, T Value) {
198 return CasesLowerImpl(Cases: {S0, S1, S2, S3, S4}, Value);
199 }
200
201 [[nodiscard]] R Default(T Value) {
202 if (Result)
203 return std::move(*Result);
204 return Value;
205 }
206
207 /// Declare default as unreachable, making sure that all cases were handled.
208 [[nodiscard]] R DefaultUnreachable(
209 const char *Message = "Fell off the end of a string-switch") {
210 if (Result)
211 return std::move(*Result);
212 llvm_unreachable(Message);
213 }
214
215 [[nodiscard]] operator R() { return DefaultUnreachable(); }
216
217private:
218 // Returns true when a match is found. If `Str` matches the `S` argument,
219 // stores the result.
220 bool CaseImpl(StringLiteral S, T &Value) {
221 if (Result)
222 return true;
223
224 if (Str != S)
225 return false;
226
227 Result = std::move(Value);
228 return true;
229 }
230
231 // Returns true when a match is found. If `Str` matches the `S` argument
232 // (case-insensitive), stores the result.
233 bool CaseLowerImpl(StringLiteral S, T &Value) {
234 if (Result)
235 return true;
236
237 if (!Str.equals_insensitive(RHS: S))
238 return false;
239
240 Result = std::move(Value);
241 return true;
242 }
243
244 StringSwitch &CasesImpl(std::initializer_list<StringLiteral> Cases,
245 T &Value) {
246 // Stop matching after the string is found.
247 for (StringLiteral S : Cases)
248 if (CaseImpl(S, Value))
249 break;
250 return *this;
251 }
252
253 StringSwitch &CasesLowerImpl(std::initializer_list<StringLiteral> Cases,
254 T &Value) {
255 // Stop matching after the string is found.
256 for (StringLiteral S : Cases)
257 if (CaseLowerImpl(S, Value))
258 break;
259 return *this;
260 }
261};
262
263} // end namespace llvm
264
265#endif // LLVM_ADT_STRINGSWITCH_H
266