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(CaseStrings: {"KIND_JOINED", "KIND_JOINED_OR_SEPARATE",
177 "KIND_JOINED_AND_SEPARATE", "KIND_REMAINING_ARGS_JOINED"},
178 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(CaseStrings: {"KIND_JOINED", "KIND_JOINED_OR_SEPARATE", "KIND_SEPARATE"}, Value: 1)
190 .Cases(CaseStrings: {"KIND_REMAINING_ARGS", "KIND_REMAINING_ARGS_JOINED",
191 "KIND_COMMAJOINED"},
192 Value: UnlimitedArgs)
193 .Case(S: "KIND_JOINED_AND_SEPARATE", Value: 2)
194 .Case(S: "KIND_MULTIARG", Value: Option->getValueAsInt(FieldName: "NumArgs"))
195 .Default(Value: 0);
196}
197
198std::string escapeRST(StringRef Str) {
199 std::string Out;
200 for (auto K : Str) {
201 if (StringRef("`*|[]\\").count(C: K))
202 Out.push_back(c: '\\');
203 Out.push_back(c: K);
204 }
205 return Out;
206}
207
208StringRef getSphinxOptionID(StringRef OptionName) {
209 return OptionName.take_while(F: [](char C) { return isalnum(C) || C == '-'; });
210}
211
212bool canSphinxCopeWithOption(const Record *Option) {
213 // HACK: Work arond sphinx's inability to cope with punctuation-only options
214 // such as /? by suppressing them from the option list.
215 for (char C : Option->getValueAsString(FieldName: "Name"))
216 if (isalnum(C))
217 return true;
218 return false;
219}
220
221void emitHeading(int Depth, std::string Heading, raw_ostream &OS) {
222 assert(Depth < 8 && "groups nested too deeply");
223 OS << Heading << '\n'
224 << std::string(Heading.size(), "=~-_'+<>"[Depth]) << "\n";
225}
226
227/// Get the value of field \p Primary, if possible. If \p Primary does not
228/// exist, get the value of \p Fallback and escape it for rST emission.
229std::string getRSTStringWithTextFallback(const Record *R, StringRef Primary,
230 StringRef Fallback) {
231 for (auto Field : {Primary, Fallback}) {
232 if (auto *V = R->getValue(Name: Field)) {
233 StringRef Value;
234 if (auto *SV = dyn_cast_or_null<StringInit>(Val: V->getValue()))
235 Value = SV->getValue();
236 if (!Value.empty())
237 return Field == Primary ? Value.str() : escapeRST(Str: Value);
238 }
239 }
240 return std::string(StringRef());
241}
242
243void emitOptionWithArgs(StringRef Prefix, const Record *Option,
244 ArrayRef<StringRef> Args, raw_ostream &OS) {
245 OS << Prefix << escapeRST(Str: Option->getValueAsString(FieldName: "Name"));
246
247 std::pair<StringRef, StringRef> Separators =
248 getSeparatorsForKind(OptionKind: Option->getValueAsDef(FieldName: "Kind"));
249
250 StringRef Separator = Separators.first;
251 for (auto Arg : Args) {
252 OS << Separator << escapeRST(Str: Arg);
253 Separator = Separators.second;
254 }
255}
256
257constexpr StringLiteral DefaultMetaVarName = "<arg>";
258
259void emitOptionName(StringRef Prefix, const Record *Option, raw_ostream &OS) {
260 // Find the arguments to list after the option.
261 unsigned NumArgs = getNumArgsForKind(OptionKind: Option->getValueAsDef(FieldName: "Kind"), Option);
262 bool HasMetaVarName = !Option->isValueUnset(FieldName: "MetaVarName");
263
264 std::vector<std::string> Args;
265 if (HasMetaVarName)
266 Args.push_back(x: std::string(Option->getValueAsString(FieldName: "MetaVarName")));
267 else if (NumArgs == 1)
268 Args.push_back(x: DefaultMetaVarName.str());
269
270 // Fill up arguments if this option didn't provide a meta var name or it
271 // supports an unlimited number of arguments. We can't see how many arguments
272 // already are in a meta var name, so assume it has right number. This is
273 // needed for JoinedAndSeparate options so that there arent't too many
274 // arguments.
275 if (!HasMetaVarName || NumArgs == UnlimitedArgs) {
276 while (Args.size() < NumArgs) {
277 Args.push_back(x: ("<arg" + Twine(Args.size() + 1) + ">").str());
278 // Use '--args <arg1> <arg2>...' if any number of args are allowed.
279 if (Args.size() == 2 && NumArgs == UnlimitedArgs) {
280 Args.back() += "...";
281 break;
282 }
283 }
284 }
285
286 emitOptionWithArgs(Prefix, Option,
287 Args: std::vector<StringRef>(Args.begin(), Args.end()), OS);
288
289 auto AliasArgs = Option->getValueAsListOfStrings(FieldName: "AliasArgs");
290 if (!AliasArgs.empty()) {
291 const Record *Alias = Option->getValueAsDef(FieldName: "Alias");
292 OS << " (equivalent to ";
293 emitOptionWithArgs(
294 Prefix: Alias->getValueAsListOfStrings(FieldName: "Prefixes").front(), Option: Alias,
295 Args: AliasArgs, OS);
296 OS << ")";
297 }
298}
299
300bool emitOptionNames(const Record *Option, raw_ostream &OS, bool EmittedAny) {
301 for (auto &Prefix : Option->getValueAsListOfStrings(FieldName: "Prefixes")) {
302 if (EmittedAny)
303 OS << ", ";
304 emitOptionName(Prefix, Option, OS);
305 EmittedAny = true;
306 }
307 return EmittedAny;
308}
309
310template <typename Fn>
311void forEachOptionName(const DocumentedOption &Option, const Record *DocInfo,
312 Fn F) {
313 F(Option.Option);
314
315 for (auto *Alias : Option.Aliases)
316 if (isOptionVisible(Option: Alias, DocInfo) &&
317 canSphinxCopeWithOption(Option: Option.Option))
318 F(Alias);
319}
320
321void emitOption(const DocumentedOption &Option, const Record *DocInfo,
322 raw_ostream &OS) {
323 if (Option.Option->getValueAsDef(FieldName: "Kind")->getName() == "KIND_UNKNOWN" ||
324 Option.Option->getValueAsDef(FieldName: "Kind")->getName() == "KIND_INPUT")
325 return;
326 if (!canSphinxCopeWithOption(Option: Option.Option))
327 return;
328
329 // HACK: Emit a different program name with each option to work around
330 // sphinx's inability to cope with options that differ only by punctuation
331 // (eg -ObjC vs -ObjC++, -G vs -G=).
332 std::vector<std::string> SphinxOptionIDs;
333 forEachOptionName(Option, DocInfo, F: [&](const Record *Option) {
334 for (auto &Prefix : Option->getValueAsListOfStrings(FieldName: "Prefixes"))
335 SphinxOptionIDs.push_back(x: std::string(getSphinxOptionID(
336 OptionName: (Prefix + Option->getValueAsString(FieldName: "Name")).str())));
337 });
338 assert(!SphinxOptionIDs.empty() && "no flags for option");
339 static std::map<std::string, int> NextSuffix;
340 int SphinxWorkaroundSuffix = NextSuffix[*llvm::max_element(
341 Range&: SphinxOptionIDs, C: [&](const std::string &A, const std::string &B) {
342 return NextSuffix[A] < NextSuffix[B];
343 })];
344 for (auto &S : SphinxOptionIDs)
345 NextSuffix[S] = SphinxWorkaroundSuffix + 1;
346
347 std::string Program = DocInfo->getValueAsString(FieldName: "Program").lower();
348 if (SphinxWorkaroundSuffix)
349 OS << ".. program:: " << Program << SphinxWorkaroundSuffix << "\n";
350
351 // Emit the names of the option.
352 OS << ".. option:: ";
353 bool EmittedAny = false;
354 forEachOptionName(Option, DocInfo, F: [&](const Record *Option) {
355 EmittedAny = emitOptionNames(Option, OS, EmittedAny);
356 });
357 if (SphinxWorkaroundSuffix)
358 OS << "\n.. program:: " << Program;
359 OS << "\n\n";
360
361 // Emit the description, if we have one.
362 const Record *R = Option.Option;
363 std::string Description;
364
365 // Prefer a program specific help string.
366 // This is a list of (visibilities, string) pairs.
367 for (const Record *VisibilityHelp :
368 R->getValueAsListOfDefs(FieldName: "HelpTextsForVariants")) {
369 // This is a list of visibilities.
370 ArrayRef<const Init *> Visibilities =
371 VisibilityHelp->getValueAsListInit(FieldName: "Visibilities")->getElements();
372
373 // See if any of the program's visibilities are in the list.
374 for (StringRef DocInfoMask :
375 DocInfo->getValueAsListOfStrings(FieldName: "VisibilityMask")) {
376 for (const Init *Visibility : Visibilities) {
377 if (Visibility->getAsUnquotedString() == DocInfoMask) {
378 // Use the first one we find.
379 Description = escapeRST(Str: VisibilityHelp->getValueAsString(FieldName: "Text"));
380 break;
381 }
382 }
383 if (!Description.empty())
384 break;
385 }
386
387 if (!Description.empty())
388 break;
389 }
390
391 // If there's not a program specific string, use the default one.
392 if (Description.empty())
393 Description = getRSTStringWithTextFallback(R, Primary: "DocBrief", Fallback: "HelpText");
394
395 if (!isa<UnsetInit>(Val: R->getValueInit(FieldName: "Values"))) {
396 if (!Description.empty() && Description.back() != '.')
397 Description.push_back(c: '.');
398
399 StringRef MetaVarName;
400 if (!isa<UnsetInit>(Val: R->getValueInit(FieldName: "MetaVarName")))
401 MetaVarName = R->getValueAsString(FieldName: "MetaVarName");
402 else
403 MetaVarName = DefaultMetaVarName;
404
405 SmallVector<StringRef> Values;
406 SplitString(Source: R->getValueAsString(FieldName: "Values"), OutFragments&: Values, Delimiters: ",");
407 Description += (" " + MetaVarName + " must be '").str();
408 if (Values.size() > 1) {
409 Description += join(Begin: Values.begin(), End: Values.end() - 1, Separator: "', '");
410 Description += "' or '";
411 }
412 Description += (Values.back() + "'.").str();
413 }
414
415 if (!Description.empty())
416 OS << Description << "\n\n";
417}
418
419void emitDocumentation(int Depth, const Documentation &Doc,
420 const Record *DocInfo, raw_ostream &OS);
421
422void emitGroup(int Depth, const DocumentedGroup &Group, const Record *DocInfo,
423 raw_ostream &OS) {
424 emitHeading(Depth,
425 Heading: getRSTStringWithTextFallback(R: Group.Group, Primary: "DocName", Fallback: "Name"), OS);
426
427 // Emit the description, if we have one.
428 std::string Description =
429 getRSTStringWithTextFallback(R: Group.Group, Primary: "DocBrief", Fallback: "HelpText");
430 if (!Description.empty())
431 OS << Description << "\n\n";
432
433 // Emit contained options and groups.
434 emitDocumentation(Depth: Depth + 1, Doc: Group, DocInfo, OS);
435}
436
437void emitDocumentation(int Depth, const Documentation &Doc,
438 const Record *DocInfo, raw_ostream &OS) {
439 for (auto &O : Doc.Options)
440 emitOption(Option: O, DocInfo, OS);
441 for (auto &G : Doc.Groups)
442 emitGroup(Depth, Group: G, DocInfo, OS);
443}
444
445} // namespace
446
447void clang::EmitClangOptDocs(const RecordKeeper &Records, raw_ostream &OS) {
448 const Record *DocInfo = Records.getDef(Name: "GlobalDocumentation");
449 if (!DocInfo) {
450 PrintFatalError(Msg: "The GlobalDocumentation top-level definition is missing, "
451 "no documentation will be generated.");
452 return;
453 }
454 OS << DocInfo->getValueAsString(FieldName: "Intro") << "\n";
455 OS << ".. program:: " << DocInfo->getValueAsString(FieldName: "Program").lower() << "\n";
456
457 emitDocumentation(Depth: 0, Doc: extractDocumentation(Records, DocInfo), DocInfo, OS);
458}
459