459 lines · cpp
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-exception6//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(FlagsField))45 if (Flag->getName() == OptionFlag)46 return true;47 if (const DefInit *DI = dyn_cast<DefInit>(Option->getValueInit("Group")))48 for (const Record *Flag : DI->getDef()->getValueAsListOfDefs(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("IgnoreFlags"))56 if (hasFlag(Option, IgnoredFlag, "Flags"))57 return false;58 for (StringRef Mask : DocInfo->getValueAsListOfStrings("VisibilityMask"))59 if (hasFlag(Option, Mask, "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 group70 // (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("Option"))77 OptionsByName[std::string(R->getValueAsString("Name"))] = R;78 79 auto Flatten = [](const Record *R) {80 return R->getValue("DocFlatten") && R->getValueAsBit("DocFlatten");81 };82 83 auto SkipFlattened = [&](const Record *R) -> const Record * {84 while (R && Flatten(R)) {85 auto *G = dyn_cast<DefInit>(R->getValueInit("Group"));86 if (!G)87 return nullptr;88 R = G->getDef();89 }90 return R;91 };92 93 for (const Record *R : Records.getAllDerivedDefinitions("OptionGroup")) {94 if (Flatten(R))95 continue;96 97 const Record *Group = nullptr;98 if (auto *G = dyn_cast<DefInit>(R->getValueInit("Group")))99 Group = SkipFlattened(G->getDef());100 GroupsInGroup[Group].push_back(R);101 }102 103 for (const Record *R : Records.getAllDerivedDefinitions("Option")) {104 if (auto *A = dyn_cast<DefInit>(R->getValueInit("Alias"))) {105 Aliases[A->getDef()].push_back(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("Name"));111 if (Name.size() >= 4) {112 if (Name.substr(0, 3) == "no-") {113 if (const Record *Opt = OptionsByName[Name.substr(3)]) {114 Aliases[Opt].push_back(R);115 continue;116 }117 }118 if (Name.substr(1, 3) == "no-") {119 if (const Record *Opt = OptionsByName[Name[0] + Name.substr(4)]) {120 Aliases[Opt].push_back(R);121 continue;122 }123 }124 }125 126 const Record *Group = nullptr;127 if (auto *G = dyn_cast<DefInit>(R->getValueInit("Group")))128 Group = SkipFlattened(G->getDef());129 OptionsInGroup[Group].push_back(R);130 }131 132 auto CompareByName = [](const Record *A, const Record *B) {133 return A->getValueAsString("Name") < B->getValueAsString("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(A, CompareByName);143 return {R, 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(Groups, 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(Options, CompareByName);163 for (const Record *O : Options)164 if (isOptionVisible(O, DocInfo))165 D.Options.push_back(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({"KIND_JOINED", "KIND_JOINED_OR_SEPARATE",177 "KIND_JOINED_AND_SEPARATE", "KIND_REMAINING_ARGS_JOINED"},178 {"", " "})179 .Case("KIND_COMMAJOINED", {"", ","})180 .Default({" ", " "});181}182 183const unsigned UnlimitedArgs = unsigned(-1);184 185// Get the number of arguments expected for an option, or -1 if any number of186// arguments are accepted.187unsigned getNumArgsForKind(const Record *OptionKind, const Record *Option) {188 return StringSwitch<unsigned>(OptionKind->getName())189 .Cases({"KIND_JOINED", "KIND_JOINED_OR_SEPARATE", "KIND_SEPARATE"}, 1)190 .Cases({"KIND_REMAINING_ARGS", "KIND_REMAINING_ARGS_JOINED",191 "KIND_COMMAJOINED"},192 UnlimitedArgs)193 .Case("KIND_JOINED_AND_SEPARATE", 2)194 .Case("KIND_MULTIARG", Option->getValueAsInt("NumArgs"))195 .Default(0);196}197 198std::string escapeRST(StringRef Str) {199 std::string Out;200 for (auto K : Str) {201 if (StringRef("`*|[]\\").count(K))202 Out.push_back('\\');203 Out.push_back(K);204 }205 return Out;206}207 208StringRef getSphinxOptionID(StringRef OptionName) {209 return OptionName.take_while([](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 options214 // such as /? by suppressing them from the option list.215 for (char C : Option->getValueAsString("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 not228/// 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(Field)) {233 StringRef Value;234 if (auto *SV = dyn_cast_or_null<StringInit>(V->getValue()))235 Value = SV->getValue();236 if (!Value.empty())237 return Field == Primary ? Value.str() : escapeRST(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(Option->getValueAsString("Name"));246 247 std::pair<StringRef, StringRef> Separators =248 getSeparatorsForKind(Option->getValueAsDef("Kind"));249 250 StringRef Separator = Separators.first;251 for (auto Arg : Args) {252 OS << Separator << escapeRST(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(Option->getValueAsDef("Kind"), Option);262 bool HasMetaVarName = !Option->isValueUnset("MetaVarName");263 264 std::vector<std::string> Args;265 if (HasMetaVarName)266 Args.push_back(std::string(Option->getValueAsString("MetaVarName")));267 else if (NumArgs == 1)268 Args.push_back(DefaultMetaVarName.str());269 270 // Fill up arguments if this option didn't provide a meta var name or it271 // supports an unlimited number of arguments. We can't see how many arguments272 // already are in a meta var name, so assume it has right number. This is273 // needed for JoinedAndSeparate options so that there arent't too many274 // arguments.275 if (!HasMetaVarName || NumArgs == UnlimitedArgs) {276 while (Args.size() < NumArgs) {277 Args.push_back(("<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 std::vector<StringRef>(Args.begin(), Args.end()), OS);288 289 auto AliasArgs = Option->getValueAsListOfStrings("AliasArgs");290 if (!AliasArgs.empty()) {291 const Record *Alias = Option->getValueAsDef("Alias");292 OS << " (equivalent to ";293 emitOptionWithArgs(294 Alias->getValueAsListOfStrings("Prefixes").front(), Alias,295 AliasArgs, OS);296 OS << ")";297 }298}299 300bool emitOptionNames(const Record *Option, raw_ostream &OS, bool EmittedAny) {301 for (auto &Prefix : Option->getValueAsListOfStrings("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(Alias, DocInfo) &&317 canSphinxCopeWithOption(Option.Option))318 F(Alias);319}320 321void emitOption(const DocumentedOption &Option, const Record *DocInfo,322 raw_ostream &OS) {323 if (Option.Option->getValueAsDef("Kind")->getName() == "KIND_UNKNOWN" ||324 Option.Option->getValueAsDef("Kind")->getName() == "KIND_INPUT")325 return;326 if (!canSphinxCopeWithOption(Option.Option))327 return;328 329 // HACK: Emit a different program name with each option to work around330 // sphinx's inability to cope with options that differ only by punctuation331 // (eg -ObjC vs -ObjC++, -G vs -G=).332 std::vector<std::string> SphinxOptionIDs;333 forEachOptionName(Option, DocInfo, [&](const Record *Option) {334 for (auto &Prefix : Option->getValueAsListOfStrings("Prefixes"))335 SphinxOptionIDs.push_back(std::string(getSphinxOptionID(336 (Prefix + Option->getValueAsString("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 SphinxOptionIDs, [&](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("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, [&](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("HelpTextsForVariants")) {369 // This is a list of visibilities.370 ArrayRef<const Init *> Visibilities =371 VisibilityHelp->getValueAsListInit("Visibilities")->getElements();372 373 // See if any of the program's visibilities are in the list.374 for (StringRef DocInfoMask :375 DocInfo->getValueAsListOfStrings("VisibilityMask")) {376 for (const Init *Visibility : Visibilities) {377 if (Visibility->getAsUnquotedString() == DocInfoMask) {378 // Use the first one we find.379 Description = escapeRST(VisibilityHelp->getValueAsString("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, "DocBrief", "HelpText");394 395 if (!isa<UnsetInit>(R->getValueInit("Values"))) {396 if (!Description.empty() && Description.back() != '.')397 Description.push_back('.');398 399 StringRef MetaVarName;400 if (!isa<UnsetInit>(R->getValueInit("MetaVarName")))401 MetaVarName = R->getValueAsString("MetaVarName");402 else403 MetaVarName = DefaultMetaVarName;404 405 SmallVector<StringRef> Values;406 SplitString(R->getValueAsString("Values"), Values, ",");407 Description += (" " + MetaVarName + " must be '").str();408 if (Values.size() > 1) {409 Description += join(Values.begin(), Values.end() - 1, "', '");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 getRSTStringWithTextFallback(Group.Group, "DocName", "Name"), OS);426 427 // Emit the description, if we have one.428 std::string Description =429 getRSTStringWithTextFallback(Group.Group, "DocBrief", "HelpText");430 if (!Description.empty())431 OS << Description << "\n\n";432 433 // Emit contained options and groups.434 emitDocumentation(Depth + 1, 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(O, DocInfo, OS);441 for (auto &G : Doc.Groups)442 emitGroup(Depth, G, DocInfo, OS);443}444 445} // namespace446 447void clang::EmitClangOptDocs(const RecordKeeper &Records, raw_ostream &OS) {448 const Record *DocInfo = Records.getDef("GlobalDocumentation");449 if (!DocInfo) {450 PrintFatalError("The GlobalDocumentation top-level definition is missing, "451 "no documentation will be generated.");452 return;453 }454 OS << DocInfo->getValueAsString("Intro") << "\n";455 OS << ".. program:: " << DocInfo->getValueAsString("Program").lower() << "\n";456 457 emitDocumentation(0, extractDocumentation(Records, DocInfo), DocInfo, OS);458}459