/Users/buildslave/jenkins/workspace/coverage/llvm-project/clang/utils/TableGen/ClangOptionDocEmitter.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | //===- ClangOptionDocEmitter.cpp - Documentation for command line flags ---===// |
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 | | // FIXME: Once this has stabilized, consider moving it to LLVM. |
8 | | // |
9 | | //===----------------------------------------------------------------------===// |
10 | | |
11 | | #include "TableGenBackends.h" |
12 | | #include "llvm/TableGen/Error.h" |
13 | | #include "llvm/ADT/STLExtras.h" |
14 | | #include "llvm/ADT/SmallString.h" |
15 | | #include "llvm/ADT/StringSwitch.h" |
16 | | #include "llvm/ADT/Twine.h" |
17 | | #include "llvm/TableGen/Record.h" |
18 | | #include "llvm/TableGen/TableGenBackend.h" |
19 | | #include <cctype> |
20 | | #include <cstring> |
21 | | #include <map> |
22 | | |
23 | | using namespace llvm; |
24 | | |
25 | | namespace { |
26 | | struct DocumentedOption { |
27 | | Record *Option; |
28 | | std::vector<Record*> Aliases; |
29 | | }; |
30 | | struct DocumentedGroup; |
31 | | struct Documentation { |
32 | | std::vector<DocumentedGroup> Groups; |
33 | | std::vector<DocumentedOption> Options; |
34 | | }; |
35 | | struct DocumentedGroup : Documentation { |
36 | | Record *Group; |
37 | | }; |
38 | | |
39 | | // Reorganize the records into a suitable form for emitting documentation. |
40 | 0 | Documentation extractDocumentation(RecordKeeper &Records) { |
41 | 0 | Documentation Result; |
42 | | |
43 | | // Build the tree of groups. The root in the tree is the fake option group |
44 | | // (Record*)nullptr, which contains all top-level groups and options. |
45 | 0 | std::map<Record*, std::vector<Record*> > OptionsInGroup; |
46 | 0 | std::map<Record*, std::vector<Record*> > GroupsInGroup; |
47 | 0 | std::map<Record*, std::vector<Record*> > Aliases; |
48 | |
|
49 | 0 | std::map<std::string, Record*> OptionsByName; |
50 | 0 | for (Record *R : Records.getAllDerivedDefinitions("Option")) |
51 | 0 | OptionsByName[std::string(R->getValueAsString("Name"))] = R; |
52 | |
|
53 | 0 | auto Flatten = [](Record *R) { |
54 | 0 | return R->getValue("DocFlatten") && R->getValueAsBit("DocFlatten"); |
55 | 0 | }; |
56 | |
|
57 | 0 | auto SkipFlattened = [&](Record *R) -> Record* { |
58 | 0 | while (R && Flatten(R)) { |
59 | 0 | auto *G = dyn_cast<DefInit>(R->getValueInit("Group")); |
60 | 0 | if (!G) |
61 | 0 | return nullptr; |
62 | 0 | R = G->getDef(); |
63 | 0 | } |
64 | 0 | return R; |
65 | 0 | }; |
66 | |
|
67 | 0 | for (Record *R : Records.getAllDerivedDefinitions("OptionGroup")) { |
68 | 0 | if (Flatten(R)) |
69 | 0 | continue; |
70 | | |
71 | 0 | Record *Group = nullptr; |
72 | 0 | if (auto *G = dyn_cast<DefInit>(R->getValueInit("Group"))) |
73 | 0 | Group = SkipFlattened(G->getDef()); |
74 | 0 | GroupsInGroup[Group].push_back(R); |
75 | 0 | } |
76 | |
|
77 | 0 | for (Record *R : Records.getAllDerivedDefinitions("Option")) { |
78 | 0 | if (auto *A = dyn_cast<DefInit>(R->getValueInit("Alias"))) { |
79 | 0 | Aliases[A->getDef()].push_back(R); |
80 | 0 | continue; |
81 | 0 | } |
82 | | |
83 | | // Pretend no-X and Xno-Y options are aliases of X and XY. |
84 | 0 | std::string Name = std::string(R->getValueAsString("Name")); |
85 | 0 | if (Name.size() >= 4) { |
86 | 0 | if (Name.substr(0, 3) == "no-" && OptionsByName[Name.substr(3)]) { |
87 | 0 | Aliases[OptionsByName[Name.substr(3)]].push_back(R); |
88 | 0 | continue; |
89 | 0 | } |
90 | 0 | if (Name.substr(1, 3) == "no-" && OptionsByName[Name[0] + Name.substr(4)]) { |
91 | 0 | Aliases[OptionsByName[Name[0] + Name.substr(4)]].push_back(R); |
92 | 0 | continue; |
93 | 0 | } |
94 | 0 | } |
95 | | |
96 | 0 | Record *Group = nullptr; |
97 | 0 | if (auto *G = dyn_cast<DefInit>(R->getValueInit("Group"))) |
98 | 0 | Group = SkipFlattened(G->getDef()); |
99 | 0 | OptionsInGroup[Group].push_back(R); |
100 | 0 | } |
101 | |
|
102 | 0 | auto CompareByName = [](Record *A, Record *B) { |
103 | 0 | return A->getValueAsString("Name") < B->getValueAsString("Name"); |
104 | 0 | }; |
105 | |
|
106 | 0 | auto CompareByLocation = [](Record *A, Record *B) { |
107 | 0 | return A->getLoc()[0].getPointer() < B->getLoc()[0].getPointer(); |
108 | 0 | }; |
109 | |
|
110 | 0 | auto DocumentationForOption = [&](Record *R) -> DocumentedOption { |
111 | 0 | auto &A = Aliases[R]; |
112 | 0 | llvm::sort(A, CompareByName); |
113 | 0 | return {R, std::move(A)}; |
114 | 0 | }; |
115 | |
|
116 | 0 | std::function<Documentation(Record *)> DocumentationForGroup = |
117 | 0 | [&](Record *R) -> Documentation { |
118 | 0 | Documentation D; |
119 | |
|
120 | 0 | auto &Groups = GroupsInGroup[R]; |
121 | 0 | llvm::sort(Groups, CompareByLocation); |
122 | 0 | for (Record *G : Groups) { |
123 | 0 | D.Groups.emplace_back(); |
124 | 0 | D.Groups.back().Group = G; |
125 | 0 | Documentation &Base = D.Groups.back(); |
126 | 0 | Base = DocumentationForGroup(G); |
127 | 0 | } |
128 | |
|
129 | 0 | auto &Options = OptionsInGroup[R]; |
130 | 0 | llvm::sort(Options, CompareByName); |
131 | 0 | for (Record *O : Options) |
132 | 0 | D.Options.push_back(DocumentationForOption(O)); |
133 | |
|
134 | 0 | return D; |
135 | 0 | }; |
136 | |
|
137 | 0 | return DocumentationForGroup(nullptr); |
138 | 0 | } |
139 | | |
140 | | // Get the first and successive separators to use for an OptionKind. |
141 | 0 | std::pair<StringRef,StringRef> getSeparatorsForKind(const Record *OptionKind) { |
142 | 0 | return StringSwitch<std::pair<StringRef, StringRef>>(OptionKind->getName()) |
143 | 0 | .Cases("KIND_JOINED", "KIND_JOINED_OR_SEPARATE", |
144 | 0 | "KIND_JOINED_AND_SEPARATE", |
145 | 0 | "KIND_REMAINING_ARGS_JOINED", {"", " "}) |
146 | 0 | .Case("KIND_COMMAJOINED", {"", ","}) |
147 | 0 | .Default({" ", " "}); |
148 | 0 | } |
149 | | |
150 | | const unsigned UnlimitedArgs = unsigned(-1); |
151 | | |
152 | | // Get the number of arguments expected for an option, or -1 if any number of |
153 | | // arguments are accepted. |
154 | 0 | unsigned getNumArgsForKind(Record *OptionKind, const Record *Option) { |
155 | 0 | return StringSwitch<unsigned>(OptionKind->getName()) |
156 | 0 | .Cases("KIND_JOINED", "KIND_JOINED_OR_SEPARATE", "KIND_SEPARATE", 1) |
157 | 0 | .Cases("KIND_REMAINING_ARGS", "KIND_REMAINING_ARGS_JOINED", |
158 | 0 | "KIND_COMMAJOINED", UnlimitedArgs) |
159 | 0 | .Case("KIND_JOINED_AND_SEPARATE", 2) |
160 | 0 | .Case("KIND_MULTIARG", Option->getValueAsInt("NumArgs")) |
161 | 0 | .Default(0); |
162 | 0 | } |
163 | | |
164 | 0 | bool hasFlag(const Record *OptionOrGroup, StringRef OptionFlag) { |
165 | 0 | for (const Record *Flag : OptionOrGroup->getValueAsListOfDefs("Flags")) |
166 | 0 | if (Flag->getName() == OptionFlag) |
167 | 0 | return true; |
168 | 0 | return false; |
169 | 0 | } |
170 | | |
171 | 0 | bool isExcluded(const Record *OptionOrGroup, const Record *DocInfo) { |
172 | | // FIXME: Provide a flag to specify the set of exclusions. |
173 | 0 | for (StringRef Exclusion : DocInfo->getValueAsListOfStrings("ExcludedFlags")) |
174 | 0 | if (hasFlag(OptionOrGroup, Exclusion)) |
175 | 0 | return true; |
176 | 0 | return false; |
177 | 0 | } |
178 | | |
179 | 0 | std::string escapeRST(StringRef Str) { |
180 | 0 | std::string Out; |
181 | 0 | for (auto K : Str) { |
182 | 0 | if (StringRef("`*|_[]\\").count(K)) |
183 | 0 | Out.push_back('\\'); |
184 | 0 | Out.push_back(K); |
185 | 0 | } |
186 | 0 | return Out; |
187 | 0 | } |
188 | | |
189 | 0 | StringRef getSphinxOptionID(StringRef OptionName) { |
190 | 0 | for (auto I = OptionName.begin(), E = OptionName.end(); I != E; ++I) |
191 | 0 | if (!isalnum(*I) && *I != '-') |
192 | 0 | return OptionName.substr(0, I - OptionName.begin()); |
193 | 0 | return OptionName; |
194 | 0 | } |
195 | | |
196 | 0 | bool canSphinxCopeWithOption(const Record *Option) { |
197 | | // HACK: Work arond sphinx's inability to cope with punctuation-only options |
198 | | // such as /? by suppressing them from the option list. |
199 | 0 | for (char C : Option->getValueAsString("Name")) |
200 | 0 | if (isalnum(C)) |
201 | 0 | return true; |
202 | 0 | return false; |
203 | 0 | } |
204 | | |
205 | 0 | void emitHeading(int Depth, std::string Heading, raw_ostream &OS) { |
206 | 0 | assert(Depth < 8 && "groups nested too deeply"); |
207 | 0 | OS << Heading << '\n' |
208 | 0 | << std::string(Heading.size(), "=~-_'+<>"[Depth]) << "\n"; |
209 | 0 | } |
210 | | |
211 | | /// Get the value of field \p Primary, if possible. If \p Primary does not |
212 | | /// exist, get the value of \p Fallback and escape it for rST emission. |
213 | | std::string getRSTStringWithTextFallback(const Record *R, StringRef Primary, |
214 | 0 | StringRef Fallback) { |
215 | 0 | for (auto Field : {Primary, Fallback}) { |
216 | 0 | if (auto *V = R->getValue(Field)) { |
217 | 0 | StringRef Value; |
218 | 0 | if (auto *SV = dyn_cast_or_null<StringInit>(V->getValue())) |
219 | 0 | Value = SV->getValue(); |
220 | 0 | if (!Value.empty()) |
221 | 0 | return Field == Primary ? Value.str() : escapeRST(Value); |
222 | 0 | } |
223 | 0 | } |
224 | 0 | return std::string(StringRef()); |
225 | 0 | } |
226 | | |
227 | | void emitOptionWithArgs(StringRef Prefix, const Record *Option, |
228 | 0 | ArrayRef<StringRef> Args, raw_ostream &OS) { |
229 | 0 | OS << Prefix << escapeRST(Option->getValueAsString("Name")); |
230 | |
|
231 | 0 | std::pair<StringRef, StringRef> Separators = |
232 | 0 | getSeparatorsForKind(Option->getValueAsDef("Kind")); |
233 | |
|
234 | 0 | StringRef Separator = Separators.first; |
235 | 0 | for (auto Arg : Args) { |
236 | 0 | OS << Separator << escapeRST(Arg); |
237 | 0 | Separator = Separators.second; |
238 | 0 | } |
239 | 0 | } |
240 | | |
241 | 0 | void emitOptionName(StringRef Prefix, const Record *Option, raw_ostream &OS) { |
242 | | // Find the arguments to list after the option. |
243 | 0 | unsigned NumArgs = getNumArgsForKind(Option->getValueAsDef("Kind"), Option); |
244 | 0 | bool HasMetaVarName = !Option->isValueUnset("MetaVarName"); |
245 | |
|
246 | 0 | std::vector<std::string> Args; |
247 | 0 | if (HasMetaVarName) |
248 | 0 | Args.push_back(std::string(Option->getValueAsString("MetaVarName"))); |
249 | 0 | else if (NumArgs == 1) |
250 | 0 | Args.push_back("<arg>"); |
251 | | |
252 | | // Fill up arguments if this option didn't provide a meta var name or it |
253 | | // supports an unlimited number of arguments. We can't see how many arguments |
254 | | // already are in a meta var name, so assume it has right number. This is |
255 | | // needed for JoinedAndSeparate options so that there arent't too many |
256 | | // arguments. |
257 | 0 | if (!HasMetaVarName || NumArgs == UnlimitedArgs) { |
258 | 0 | while (Args.size() < NumArgs) { |
259 | 0 | Args.push_back(("<arg" + Twine(Args.size() + 1) + ">").str()); |
260 | | // Use '--args <arg1> <arg2>...' if any number of args are allowed. |
261 | 0 | if (Args.size() == 2 && NumArgs == UnlimitedArgs) { |
262 | 0 | Args.back() += "..."; |
263 | 0 | break; |
264 | 0 | } |
265 | 0 | } |
266 | 0 | } |
267 | |
|
268 | 0 | emitOptionWithArgs(Prefix, Option, std::vector<StringRef>(Args.begin(), Args.end()), OS); |
269 | |
|
270 | 0 | auto AliasArgs = Option->getValueAsListOfStrings("AliasArgs"); |
271 | 0 | if (!AliasArgs.empty()) { |
272 | 0 | Record *Alias = Option->getValueAsDef("Alias"); |
273 | 0 | OS << " (equivalent to "; |
274 | 0 | emitOptionWithArgs( |
275 | 0 | Alias->getValueAsListOfStrings("Prefixes").front(), Alias, |
276 | 0 | AliasArgs, OS); |
277 | 0 | OS << ")"; |
278 | 0 | } |
279 | 0 | } |
280 | | |
281 | 0 | bool emitOptionNames(const Record *Option, raw_ostream &OS, bool EmittedAny) { |
282 | 0 | for (auto &Prefix : Option->getValueAsListOfStrings("Prefixes")) { |
283 | 0 | if (EmittedAny) |
284 | 0 | OS << ", "; |
285 | 0 | emitOptionName(Prefix, Option, OS); |
286 | 0 | EmittedAny = true; |
287 | 0 | } |
288 | 0 | return EmittedAny; |
289 | 0 | } |
290 | | |
291 | | template <typename Fn> |
292 | | void forEachOptionName(const DocumentedOption &Option, const Record *DocInfo, |
293 | 0 | Fn F) { |
294 | 0 | F(Option.Option); |
295 | |
|
296 | 0 | for (auto *Alias : Option.Aliases) |
297 | 0 | if (!isExcluded(Alias, DocInfo) && canSphinxCopeWithOption(Option.Option)) |
298 | 0 | F(Alias); |
299 | 0 | } Unexecuted instantiation: ClangOptionDocEmitter.cpp:void (anonymous namespace)::forEachOptionName<(anonymous namespace)::emitOption((anonymous namespace)::DocumentedOption const&, llvm::Record const*, llvm::raw_ostream&)::$_4>((anonymous namespace)::DocumentedOption const&, llvm::Record const*, (anonymous namespace)::emitOption((anonymous namespace)::DocumentedOption const&, llvm::Record const*, llvm::raw_ostream&)::$_4) Unexecuted instantiation: ClangOptionDocEmitter.cpp:void (anonymous namespace)::forEachOptionName<(anonymous namespace)::emitOption((anonymous namespace)::DocumentedOption const&, llvm::Record const*, llvm::raw_ostream&)::$_5>((anonymous namespace)::DocumentedOption const&, llvm::Record const*, (anonymous namespace)::emitOption((anonymous namespace)::DocumentedOption const&, llvm::Record const*, llvm::raw_ostream&)::$_5) |
300 | | |
301 | | void emitOption(const DocumentedOption &Option, const Record *DocInfo, |
302 | 0 | raw_ostream &OS) { |
303 | 0 | if (isExcluded(Option.Option, DocInfo)) |
304 | 0 | return; |
305 | 0 | if (Option.Option->getValueAsDef("Kind")->getName() == "KIND_UNKNOWN" || |
306 | 0 | Option.Option->getValueAsDef("Kind")->getName() == "KIND_INPUT") |
307 | 0 | return; |
308 | 0 | if (!canSphinxCopeWithOption(Option.Option)) |
309 | 0 | return; |
310 | | |
311 | | // HACK: Emit a different program name with each option to work around |
312 | | // sphinx's inability to cope with options that differ only by punctuation |
313 | | // (eg -ObjC vs -ObjC++, -G vs -G=). |
314 | 0 | std::vector<std::string> SphinxOptionIDs; |
315 | 0 | forEachOptionName(Option, DocInfo, [&](const Record *Option) { |
316 | 0 | for (auto &Prefix : Option->getValueAsListOfStrings("Prefixes")) |
317 | 0 | SphinxOptionIDs.push_back(std::string(getSphinxOptionID( |
318 | 0 | (Prefix + Option->getValueAsString("Name")).str()))); |
319 | 0 | }); |
320 | 0 | assert(!SphinxOptionIDs.empty() && "no flags for option"); |
321 | 0 | static std::map<std::string, int> NextSuffix; |
322 | 0 | int SphinxWorkaroundSuffix = NextSuffix[*std::max_element( |
323 | 0 | SphinxOptionIDs.begin(), SphinxOptionIDs.end(), |
324 | 0 | [&](const std::string &A, const std::string &B) { |
325 | 0 | return NextSuffix[A] < NextSuffix[B]; |
326 | 0 | })]; |
327 | 0 | for (auto &S : SphinxOptionIDs) |
328 | 0 | NextSuffix[S] = SphinxWorkaroundSuffix + 1; |
329 | 0 | if (SphinxWorkaroundSuffix) |
330 | 0 | OS << ".. program:: " << DocInfo->getValueAsString("Program") |
331 | 0 | << SphinxWorkaroundSuffix << "\n"; |
332 | | |
333 | | // Emit the names of the option. |
334 | 0 | OS << ".. option:: "; |
335 | 0 | bool EmittedAny = false; |
336 | 0 | forEachOptionName(Option, DocInfo, [&](const Record *Option) { |
337 | 0 | EmittedAny = emitOptionNames(Option, OS, EmittedAny); |
338 | 0 | }); |
339 | 0 | if (SphinxWorkaroundSuffix) |
340 | 0 | OS << "\n.. program:: " << DocInfo->getValueAsString("Program"); |
341 | 0 | OS << "\n\n"; |
342 | | |
343 | | // Emit the description, if we have one. |
344 | 0 | std::string Description = |
345 | 0 | getRSTStringWithTextFallback(Option.Option, "DocBrief", "HelpText"); |
346 | 0 | if (!Description.empty()) |
347 | 0 | OS << Description << "\n\n"; |
348 | 0 | } |
349 | | |
350 | | void emitDocumentation(int Depth, const Documentation &Doc, |
351 | | const Record *DocInfo, raw_ostream &OS); |
352 | | |
353 | | void emitGroup(int Depth, const DocumentedGroup &Group, const Record *DocInfo, |
354 | 0 | raw_ostream &OS) { |
355 | 0 | if (isExcluded(Group.Group, DocInfo)) |
356 | 0 | return; |
357 | | |
358 | 0 | emitHeading(Depth, |
359 | 0 | getRSTStringWithTextFallback(Group.Group, "DocName", "Name"), OS); |
360 | | |
361 | | // Emit the description, if we have one. |
362 | 0 | std::string Description = |
363 | 0 | getRSTStringWithTextFallback(Group.Group, "DocBrief", "HelpText"); |
364 | 0 | if (!Description.empty()) |
365 | 0 | OS << Description << "\n\n"; |
366 | | |
367 | | // Emit contained options and groups. |
368 | 0 | emitDocumentation(Depth + 1, Group, DocInfo, OS); |
369 | 0 | } |
370 | | |
371 | | void emitDocumentation(int Depth, const Documentation &Doc, |
372 | 0 | const Record *DocInfo, raw_ostream &OS) { |
373 | 0 | for (auto &O : Doc.Options) |
374 | 0 | emitOption(O, DocInfo, OS); |
375 | 0 | for (auto &G : Doc.Groups) |
376 | 0 | emitGroup(Depth, G, DocInfo, OS); |
377 | 0 | } |
378 | | |
379 | | } // namespace |
380 | | |
381 | 0 | void clang::EmitClangOptDocs(RecordKeeper &Records, raw_ostream &OS) { |
382 | 0 | const Record *DocInfo = Records.getDef("GlobalDocumentation"); |
383 | 0 | if (!DocInfo) { |
384 | 0 | PrintFatalError("The GlobalDocumentation top-level definition is missing, " |
385 | 0 | "no documentation will be generated."); |
386 | 0 | return; |
387 | 0 | } |
388 | 0 | OS << DocInfo->getValueAsString("Intro") << "\n"; |
389 | 0 | OS << ".. program:: " << DocInfo->getValueAsString("Program") << "\n"; |
390 | |
|
391 | 0 | emitDocumentation(0, extractDocumentation(Records), DocInfo, OS); |
392 | 0 | } |