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 escapePlainTextForMarkdown(StringRef Str) {
199 std::string Out;
200 for (char C : Str) {
201 if (StringRef("*[]\\<>").count(C))
202 Out.push_back(c: '\\');
203 Out.push_back(c: C);
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, const std::string &Heading, raw_ostream &OS) {
222 assert(Depth < 5 && "groups nested too deeply");
223 OS << std::string(Depth + 2, '#') << ' ' << Heading << "\n\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 Markdown emission.
228std::string getMarkdownStringWithTextFallback(const Record *R,
229 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()
238 : escapePlainTextForMarkdown(Str: Value);
239 }
240 }
241 return std::string(StringRef());
242}
243
244// The Sphinx option directive contents do not need to be escaped. They use
245// standard usage conventions, where angle brackets are values, and square
246// brackets are optional arguments. See
247// https://www.sphinx-doc.org/en/master/usage/domains/standard.html#directive-option
248void emitOptionWithArgs(StringRef Prefix, const Record *Option,
249 ArrayRef<StringRef> Args, raw_ostream &OS) {
250 OS << Prefix << Option->getValueAsString(FieldName: "Name");
251
252 std::pair<StringRef, StringRef> Separators =
253 getSeparatorsForKind(OptionKind: Option->getValueAsDef(FieldName: "Kind"));
254
255 StringRef Separator = Separators.first;
256 for (auto Arg : Args) {
257 OS << Separator << Arg;
258 Separator = Separators.second;
259 }
260}
261
262constexpr StringLiteral DefaultMetaVarName = "<arg>";
263
264void emitOptionName(StringRef Prefix, const Record *Option, raw_ostream &OS) {
265 // Find the arguments to list after the option.
266 unsigned NumArgs = getNumArgsForKind(OptionKind: Option->getValueAsDef(FieldName: "Kind"), Option);
267 bool HasMetaVarName = !Option->isValueUnset(FieldName: "MetaVarName");
268
269 std::vector<std::string> Args;
270 if (HasMetaVarName)
271 Args.push_back(x: std::string(Option->getValueAsString(FieldName: "MetaVarName")));
272 else if (NumArgs == 1)
273 Args.push_back(x: DefaultMetaVarName.str());
274
275 // Fill up arguments if this option didn't provide a meta var name or it
276 // supports an unlimited number of arguments. We can't see how many arguments
277 // already are in a meta var name, so assume it has right number. This is
278 // needed for JoinedAndSeparate options so that there arent't too many
279 // arguments.
280 if (!HasMetaVarName || NumArgs == UnlimitedArgs) {
281 while (Args.size() < NumArgs) {
282 Args.push_back(x: ("<arg" + Twine(Args.size() + 1) + ">").str());
283 // Use '--args <arg1> <arg2>...' if any number of args are allowed.
284 if (Args.size() == 2 && NumArgs == UnlimitedArgs) {
285 Args.back() += "...";
286 break;
287 }
288 }
289 }
290
291 emitOptionWithArgs(Prefix, Option,
292 Args: std::vector<StringRef>(Args.begin(), Args.end()), OS);
293
294 auto AliasArgs = Option->getValueAsListOfStrings(FieldName: "AliasArgs");
295 if (!AliasArgs.empty()) {
296 const Record *Alias = Option->getValueAsDef(FieldName: "Alias");
297 OS << " (equivalent to ";
298 emitOptionWithArgs(
299 Prefix: Alias->getValueAsListOfStrings(FieldName: "Prefixes").front(), Option: Alias,
300 Args: AliasArgs, OS);
301 OS << ")";
302 }
303}
304
305bool emitOptionNames(const Record *Option, raw_ostream &OS, bool EmittedAny) {
306 for (auto &Prefix : Option->getValueAsListOfStrings(FieldName: "Prefixes")) {
307 if (EmittedAny)
308 OS << ", ";
309 emitOptionName(Prefix, Option, OS);
310 EmittedAny = true;
311 }
312 return EmittedAny;
313}
314
315template <typename Fn>
316void forEachOptionName(const DocumentedOption &Option, const Record *DocInfo,
317 Fn F) {
318 F(Option.Option);
319
320 for (auto *Alias : Option.Aliases)
321 if (isOptionVisible(Option: Alias, DocInfo) &&
322 canSphinxCopeWithOption(Option: Option.Option))
323 F(Alias);
324}
325
326void emitOption(const DocumentedOption &Option, const Record *DocInfo,
327 raw_ostream &OS) {
328 if (Option.Option->getValueAsDef(FieldName: "Kind")->getName() == "KIND_UNKNOWN" ||
329 Option.Option->getValueAsDef(FieldName: "Kind")->getName() == "KIND_INPUT")
330 return;
331 if (!canSphinxCopeWithOption(Option: Option.Option))
332 return;
333
334 // HACK: Emit a different program name with each option to work around
335 // sphinx's inability to cope with options that differ only by punctuation
336 // (eg -ObjC vs -ObjC++, -G vs -G=).
337 std::vector<std::string> SphinxOptionIDs;
338 forEachOptionName(Option, DocInfo, F: [&](const Record *Option) {
339 for (auto &Prefix : Option->getValueAsListOfStrings(FieldName: "Prefixes"))
340 SphinxOptionIDs.push_back(x: std::string(getSphinxOptionID(
341 OptionName: (Prefix + Option->getValueAsString(FieldName: "Name")).str())));
342 });
343 assert(!SphinxOptionIDs.empty() && "no flags for option");
344 static std::map<std::string, int> NextSuffix;
345 int SphinxWorkaroundSuffix = NextSuffix[*llvm::max_element(
346 Range&: SphinxOptionIDs, C: [&](const std::string &A, const std::string &B) {
347 return NextSuffix[A] < NextSuffix[B];
348 })];
349 for (auto &S : SphinxOptionIDs)
350 NextSuffix[S] = SphinxWorkaroundSuffix + 1;
351
352 std::string Program = DocInfo->getValueAsString(FieldName: "Program").lower();
353 if (SphinxWorkaroundSuffix)
354 OS << ":::{program} " << Program << SphinxWorkaroundSuffix << "\n:::\n\n";
355
356 // Emit the names of the option.
357 OS << ":::{option} ";
358 bool EmittedAny = false;
359 forEachOptionName(Option, DocInfo, F: [&](const Record *Option) {
360 EmittedAny = emitOptionNames(Option, OS, EmittedAny);
361 });
362 OS << "\n:::\n\n";
363
364 // Emit the description, if we have one.
365 const Record *R = Option.Option;
366 std::string Description;
367
368 // Prefer a program specific help string.
369 // This is a list of (visibilities, string) pairs.
370 for (const Record *VisibilityHelp :
371 R->getValueAsListOfDefs(FieldName: "HelpTextsForVariants")) {
372 // This is a list of visibilities.
373 ArrayRef<const Init *> Visibilities =
374 VisibilityHelp->getValueAsListInit(FieldName: "Visibilities")->getElements();
375
376 // See if any of the program's visibilities are in the list.
377 for (StringRef DocInfoMask :
378 DocInfo->getValueAsListOfStrings(FieldName: "VisibilityMask")) {
379 for (const Init *Visibility : Visibilities) {
380 if (Visibility->getAsUnquotedString() == DocInfoMask) {
381 // Use the first one we find.
382 Description = escapePlainTextForMarkdown(
383 Str: VisibilityHelp->getValueAsString(FieldName: "Text"));
384 break;
385 }
386 }
387 if (!Description.empty())
388 break;
389 }
390
391 if (!Description.empty())
392 break;
393 }
394
395 // If there's not a program specific string, use the default one.
396 if (Description.empty())
397 Description = getMarkdownStringWithTextFallback(R, Primary: "DocBrief", Fallback: "HelpText");
398
399 if (!isa<UnsetInit>(Val: R->getValueInit(FieldName: "Values"))) {
400 if (!Description.empty() && Description.back() != '.')
401 Description.push_back(c: '.');
402
403 StringRef MetaVarName;
404 if (!isa<UnsetInit>(Val: R->getValueInit(FieldName: "MetaVarName")))
405 MetaVarName = R->getValueAsString(FieldName: "MetaVarName");
406 else
407 MetaVarName = DefaultMetaVarName;
408
409 SmallVector<StringRef> Values;
410 SplitString(Source: R->getValueAsString(FieldName: "Values"), OutFragments&: Values, Delimiters: ",");
411 Description += " " + escapePlainTextForMarkdown(Str: MetaVarName) + " must be '";
412 if (Values.size() > 1) {
413 for (auto [I, Value] : enumerate(First: drop_end(RangeOrContainer&: Values))) {
414 if (I)
415 Description += "', '";
416 Description += escapePlainTextForMarkdown(Str: Value);
417 }
418 Description += "' or '";
419 }
420 Description += escapePlainTextForMarkdown(Str: Values.back()) + "'.";
421 }
422
423 if (!Description.empty())
424 OS << Description << "\n\n";
425
426 if (SphinxWorkaroundSuffix)
427 OS << ":::{program} " << Program << "\n:::\n\n";
428}
429
430void emitDocumentation(int Depth, const Documentation &Doc,
431 const Record *DocInfo, raw_ostream &OS);
432
433void emitGroup(int Depth, const DocumentedGroup &Group, const Record *DocInfo,
434 raw_ostream &OS) {
435 emitHeading(Depth,
436 Heading: getMarkdownStringWithTextFallback(R: Group.Group, Primary: "DocName", Fallback: "Name"),
437 OS);
438
439 // Emit the description, if we have one.
440 std::string Description =
441 getMarkdownStringWithTextFallback(R: Group.Group, Primary: "DocBrief", Fallback: "HelpText");
442 if (!Description.empty())
443 OS << Description << "\n\n";
444
445 // Emit contained options and groups.
446 emitDocumentation(Depth: Depth + 1, Doc: Group, DocInfo, OS);
447}
448
449void emitDocumentation(int Depth, const Documentation &Doc,
450 const Record *DocInfo, raw_ostream &OS) {
451 for (auto &O : Doc.Options)
452 emitOption(Option: O, DocInfo, OS);
453 for (auto &G : Doc.Groups)
454 emitGroup(Depth, Group: G, DocInfo, OS);
455}
456
457} // namespace
458
459void clang::EmitClangOptDocs(const RecordKeeper &Records, raw_ostream &OS) {
460 const Record *DocInfo = Records.getDef(Name: "GlobalDocumentation");
461 if (!DocInfo) {
462 PrintFatalError(Msg: "The GlobalDocumentation top-level definition is missing, "
463 "no documentation will be generated.");
464 return;
465 }
466 OS << DocInfo->getValueAsString(FieldName: "Intro") << "\n";
467 OS << ":::{program} " << DocInfo->getValueAsString(FieldName: "Program").lower()
468 << "\n:::\n\n";
469
470 emitDocumentation(Depth: 0, Doc: extractDocumentation(Records, DocInfo), DocInfo, OS);
471}
472