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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of clang/utils/TableGen/ClangOptionDocEmitter.cpp