123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444 |
- //===- ClangOptionDocEmitter.cpp - Documentation for command line flags ---===//
- //
- // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
- // See https://llvm.org/LICENSE.txt for license information.
- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- //
- // FIXME: Once this has stabilized, consider moving it to LLVM.
- //
- //===----------------------------------------------------------------------===//
- #include "TableGenBackends.h"
- #include "llvm/TableGen/Error.h"
- #include "llvm/ADT/STLExtras.h"
- #include "llvm/ADT/SmallString.h"
- #include "llvm/ADT/StringSwitch.h"
- #include "llvm/ADT/Twine.h"
- #include "llvm/TableGen/Record.h"
- #include "llvm/TableGen/TableGenBackend.h"
- #include <cctype>
- #include <cstring>
- #include <map>
- using namespace llvm;
- namespace {
- struct DocumentedOption {
- Record *Option;
- std::vector<Record*> Aliases;
- };
- struct DocumentedGroup;
- struct Documentation {
- std::vector<DocumentedGroup> Groups;
- std::vector<DocumentedOption> Options;
- };
- struct DocumentedGroup : Documentation {
- Record *Group;
- };
- // Reorganize the records into a suitable form for emitting documentation.
- Documentation extractDocumentation(RecordKeeper &Records) {
- Documentation Result;
- // Build the tree of groups. The root in the tree is the fake option group
- // (Record*)nullptr, which contains all top-level groups and options.
- std::map<Record*, std::vector<Record*> > OptionsInGroup;
- std::map<Record*, std::vector<Record*> > GroupsInGroup;
- std::map<Record*, std::vector<Record*> > Aliases;
- std::map<std::string, Record*> OptionsByName;
- for (Record *R : Records.getAllDerivedDefinitions("Option"))
- OptionsByName[std::string(R->getValueAsString("Name"))] = R;
- auto Flatten = [](Record *R) {
- return R->getValue("DocFlatten") && R->getValueAsBit("DocFlatten");
- };
- auto SkipFlattened = [&](Record *R) -> Record* {
- while (R && Flatten(R)) {
- auto *G = dyn_cast<DefInit>(R->getValueInit("Group"));
- if (!G)
- return nullptr;
- R = G->getDef();
- }
- return R;
- };
- for (Record *R : Records.getAllDerivedDefinitions("OptionGroup")) {
- if (Flatten(R))
- continue;
- Record *Group = nullptr;
- if (auto *G = dyn_cast<DefInit>(R->getValueInit("Group")))
- Group = SkipFlattened(G->getDef());
- GroupsInGroup[Group].push_back(R);
- }
- for (Record *R : Records.getAllDerivedDefinitions("Option")) {
- if (auto *A = dyn_cast<DefInit>(R->getValueInit("Alias"))) {
- Aliases[A->getDef()].push_back(R);
- continue;
- }
- // Pretend no-X and Xno-Y options are aliases of X and XY.
- std::string Name = std::string(R->getValueAsString("Name"));
- if (Name.size() >= 4) {
- if (Name.substr(0, 3) == "no-" && OptionsByName[Name.substr(3)]) {
- Aliases[OptionsByName[Name.substr(3)]].push_back(R);
- continue;
- }
- if (Name.substr(1, 3) == "no-" && OptionsByName[Name[0] + Name.substr(4)]) {
- Aliases[OptionsByName[Name[0] + Name.substr(4)]].push_back(R);
- continue;
- }
- }
- Record *Group = nullptr;
- if (auto *G = dyn_cast<DefInit>(R->getValueInit("Group")))
- Group = SkipFlattened(G->getDef());
- OptionsInGroup[Group].push_back(R);
- }
- auto CompareByName = [](Record *A, Record *B) {
- return A->getValueAsString("Name") < B->getValueAsString("Name");
- };
- auto CompareByLocation = [](Record *A, Record *B) {
- return A->getLoc()[0].getPointer() < B->getLoc()[0].getPointer();
- };
- auto DocumentationForOption = [&](Record *R) -> DocumentedOption {
- auto &A = Aliases[R];
- llvm::sort(A, CompareByName);
- return {R, std::move(A)};
- };
- std::function<Documentation(Record *)> DocumentationForGroup =
- [&](Record *R) -> Documentation {
- Documentation D;
- auto &Groups = GroupsInGroup[R];
- llvm::sort(Groups, CompareByLocation);
- for (Record *G : Groups) {
- D.Groups.emplace_back();
- D.Groups.back().Group = G;
- Documentation &Base = D.Groups.back();
- Base = DocumentationForGroup(G);
- }
- auto &Options = OptionsInGroup[R];
- llvm::sort(Options, CompareByName);
- for (Record *O : Options)
- D.Options.push_back(DocumentationForOption(O));
- return D;
- };
- return DocumentationForGroup(nullptr);
- }
- // Get the first and successive separators to use for an OptionKind.
- std::pair<StringRef,StringRef> getSeparatorsForKind(const Record *OptionKind) {
- return StringSwitch<std::pair<StringRef, StringRef>>(OptionKind->getName())
- .Cases("KIND_JOINED", "KIND_JOINED_OR_SEPARATE",
- "KIND_JOINED_AND_SEPARATE",
- "KIND_REMAINING_ARGS_JOINED", {"", " "})
- .Case("KIND_COMMAJOINED", {"", ","})
- .Default({" ", " "});
- }
- const unsigned UnlimitedArgs = unsigned(-1);
- // Get the number of arguments expected for an option, or -1 if any number of
- // arguments are accepted.
- unsigned getNumArgsForKind(Record *OptionKind, const Record *Option) {
- return StringSwitch<unsigned>(OptionKind->getName())
- .Cases("KIND_JOINED", "KIND_JOINED_OR_SEPARATE", "KIND_SEPARATE", 1)
- .Cases("KIND_REMAINING_ARGS", "KIND_REMAINING_ARGS_JOINED",
- "KIND_COMMAJOINED", UnlimitedArgs)
- .Case("KIND_JOINED_AND_SEPARATE", 2)
- .Case("KIND_MULTIARG", Option->getValueAsInt("NumArgs"))
- .Default(0);
- }
- bool hasFlag(const Record *OptionOrGroup, StringRef OptionFlag) {
- for (const Record *Flag : OptionOrGroup->getValueAsListOfDefs("Flags"))
- if (Flag->getName() == OptionFlag)
- return true;
- return false;
- }
- bool isIncluded(const Record *OptionOrGroup, const Record *DocInfo) {
- assert(DocInfo->getValue("IncludedFlags") && "Missing includeFlags");
- for (StringRef Inclusion : DocInfo->getValueAsListOfStrings("IncludedFlags"))
- if (hasFlag(OptionOrGroup, Inclusion))
- return true;
- return false;
- }
- bool isGroupIncluded(const DocumentedGroup &Group, const Record *DocInfo) {
- if (isIncluded(Group.Group, DocInfo))
- return true;
- for (auto &O : Group.Options)
- if (isIncluded(O.Option, DocInfo))
- return true;
- for (auto &G : Group.Groups) {
- if (isIncluded(G.Group, DocInfo))
- return true;
- if (isGroupIncluded(G, DocInfo))
- return true;
- }
- return false;
- }
- bool isExcluded(const Record *OptionOrGroup, const Record *DocInfo) {
- // FIXME: Provide a flag to specify the set of exclusions.
- for (StringRef Exclusion : DocInfo->getValueAsListOfStrings("ExcludedFlags"))
- if (hasFlag(OptionOrGroup, Exclusion))
- return true;
- return false;
- }
- std::string escapeRST(StringRef Str) {
- std::string Out;
- for (auto K : Str) {
- if (StringRef("`*|_[]\\").count(K))
- Out.push_back('\\');
- Out.push_back(K);
- }
- return Out;
- }
- StringRef getSphinxOptionID(StringRef OptionName) {
- for (auto I = OptionName.begin(), E = OptionName.end(); I != E; ++I)
- if (!isalnum(*I) && *I != '-')
- return OptionName.substr(0, I - OptionName.begin());
- return OptionName;
- }
- bool canSphinxCopeWithOption(const Record *Option) {
- // HACK: Work arond sphinx's inability to cope with punctuation-only options
- // such as /? by suppressing them from the option list.
- for (char C : Option->getValueAsString("Name"))
- if (isalnum(C))
- return true;
- return false;
- }
- void emitHeading(int Depth, std::string Heading, raw_ostream &OS) {
- assert(Depth < 8 && "groups nested too deeply");
- OS << Heading << '\n'
- << std::string(Heading.size(), "=~-_'+<>"[Depth]) << "\n";
- }
- /// Get the value of field \p Primary, if possible. If \p Primary does not
- /// exist, get the value of \p Fallback and escape it for rST emission.
- std::string getRSTStringWithTextFallback(const Record *R, StringRef Primary,
- StringRef Fallback) {
- for (auto Field : {Primary, Fallback}) {
- if (auto *V = R->getValue(Field)) {
- StringRef Value;
- if (auto *SV = dyn_cast_or_null<StringInit>(V->getValue()))
- Value = SV->getValue();
- if (!Value.empty())
- return Field == Primary ? Value.str() : escapeRST(Value);
- }
- }
- return std::string(StringRef());
- }
- void emitOptionWithArgs(StringRef Prefix, const Record *Option,
- ArrayRef<StringRef> Args, raw_ostream &OS) {
- OS << Prefix << escapeRST(Option->getValueAsString("Name"));
- std::pair<StringRef, StringRef> Separators =
- getSeparatorsForKind(Option->getValueAsDef("Kind"));
- StringRef Separator = Separators.first;
- for (auto Arg : Args) {
- OS << Separator << escapeRST(Arg);
- Separator = Separators.second;
- }
- }
- constexpr StringLiteral DefaultMetaVarName = "<arg>";
- void emitOptionName(StringRef Prefix, const Record *Option, raw_ostream &OS) {
- // Find the arguments to list after the option.
- unsigned NumArgs = getNumArgsForKind(Option->getValueAsDef("Kind"), Option);
- bool HasMetaVarName = !Option->isValueUnset("MetaVarName");
- std::vector<std::string> Args;
- if (HasMetaVarName)
- Args.push_back(std::string(Option->getValueAsString("MetaVarName")));
- else if (NumArgs == 1)
- Args.push_back(DefaultMetaVarName.str());
- // Fill up arguments if this option didn't provide a meta var name or it
- // supports an unlimited number of arguments. We can't see how many arguments
- // already are in a meta var name, so assume it has right number. This is
- // needed for JoinedAndSeparate options so that there arent't too many
- // arguments.
- if (!HasMetaVarName || NumArgs == UnlimitedArgs) {
- while (Args.size() < NumArgs) {
- Args.push_back(("<arg" + Twine(Args.size() + 1) + ">").str());
- // Use '--args <arg1> <arg2>...' if any number of args are allowed.
- if (Args.size() == 2 && NumArgs == UnlimitedArgs) {
- Args.back() += "...";
- break;
- }
- }
- }
- emitOptionWithArgs(Prefix, Option, std::vector<StringRef>(Args.begin(), Args.end()), OS);
- auto AliasArgs = Option->getValueAsListOfStrings("AliasArgs");
- if (!AliasArgs.empty()) {
- Record *Alias = Option->getValueAsDef("Alias");
- OS << " (equivalent to ";
- emitOptionWithArgs(
- Alias->getValueAsListOfStrings("Prefixes").front(), Alias,
- AliasArgs, OS);
- OS << ")";
- }
- }
- bool emitOptionNames(const Record *Option, raw_ostream &OS, bool EmittedAny) {
- for (auto &Prefix : Option->getValueAsListOfStrings("Prefixes")) {
- if (EmittedAny)
- OS << ", ";
- emitOptionName(Prefix, Option, OS);
- EmittedAny = true;
- }
- return EmittedAny;
- }
- template <typename Fn>
- void forEachOptionName(const DocumentedOption &Option, const Record *DocInfo,
- Fn F) {
- F(Option.Option);
- for (auto *Alias : Option.Aliases)
- if (!isExcluded(Alias, DocInfo) && canSphinxCopeWithOption(Option.Option))
- F(Alias);
- }
- void emitOption(const DocumentedOption &Option, const Record *DocInfo,
- raw_ostream &OS) {
- if (isExcluded(Option.Option, DocInfo))
- return;
- if (DocInfo->getValue("IncludedFlags") && !isIncluded(Option.Option, DocInfo))
- return;
- if (Option.Option->getValueAsDef("Kind")->getName() == "KIND_UNKNOWN" ||
- Option.Option->getValueAsDef("Kind")->getName() == "KIND_INPUT")
- return;
- if (!canSphinxCopeWithOption(Option.Option))
- return;
- // HACK: Emit a different program name with each option to work around
- // sphinx's inability to cope with options that differ only by punctuation
- // (eg -ObjC vs -ObjC++, -G vs -G=).
- std::vector<std::string> SphinxOptionIDs;
- forEachOptionName(Option, DocInfo, [&](const Record *Option) {
- for (auto &Prefix : Option->getValueAsListOfStrings("Prefixes"))
- SphinxOptionIDs.push_back(std::string(getSphinxOptionID(
- (Prefix + Option->getValueAsString("Name")).str())));
- });
- assert(!SphinxOptionIDs.empty() && "no flags for option");
- static std::map<std::string, int> NextSuffix;
- int SphinxWorkaroundSuffix = NextSuffix[*std::max_element(
- SphinxOptionIDs.begin(), SphinxOptionIDs.end(),
- [&](const std::string &A, const std::string &B) {
- return NextSuffix[A] < NextSuffix[B];
- })];
- for (auto &S : SphinxOptionIDs)
- NextSuffix[S] = SphinxWorkaroundSuffix + 1;
- if (SphinxWorkaroundSuffix)
- OS << ".. program:: " << DocInfo->getValueAsString("Program")
- << SphinxWorkaroundSuffix << "\n";
- // Emit the names of the option.
- OS << ".. option:: ";
- bool EmittedAny = false;
- forEachOptionName(Option, DocInfo, [&](const Record *Option) {
- EmittedAny = emitOptionNames(Option, OS, EmittedAny);
- });
- if (SphinxWorkaroundSuffix)
- OS << "\n.. program:: " << DocInfo->getValueAsString("Program");
- OS << "\n\n";
- // Emit the description, if we have one.
- const Record *R = Option.Option;
- std::string Description =
- getRSTStringWithTextFallback(R, "DocBrief", "HelpText");
- if (!isa<UnsetInit>(R->getValueInit("Values"))) {
- if (!Description.empty() && Description.back() != '.')
- Description.push_back('.');
- StringRef MetaVarName;
- if (!isa<UnsetInit>(R->getValueInit("MetaVarName")))
- MetaVarName = R->getValueAsString("MetaVarName");
- else
- MetaVarName = DefaultMetaVarName;
- SmallVector<StringRef> Values;
- SplitString(R->getValueAsString("Values"), Values, ",");
- Description += (" " + MetaVarName + " must be '").str();
- if (Values.size() > 1) {
- Description += join(Values.begin(), Values.end() - 1, "', '");
- Description += "' or '";
- }
- Description += (Values.back() + "'.").str();
- }
- if (!Description.empty())
- OS << Description << "\n\n";
- }
- void emitDocumentation(int Depth, const Documentation &Doc,
- const Record *DocInfo, raw_ostream &OS);
- void emitGroup(int Depth, const DocumentedGroup &Group, const Record *DocInfo,
- raw_ostream &OS) {
- if (isExcluded(Group.Group, DocInfo))
- return;
- if (DocInfo->getValue("IncludedFlags") && !isGroupIncluded(Group, DocInfo))
- return;
- emitHeading(Depth,
- getRSTStringWithTextFallback(Group.Group, "DocName", "Name"), OS);
- // Emit the description, if we have one.
- std::string Description =
- getRSTStringWithTextFallback(Group.Group, "DocBrief", "HelpText");
- if (!Description.empty())
- OS << Description << "\n\n";
- // Emit contained options and groups.
- emitDocumentation(Depth + 1, Group, DocInfo, OS);
- }
- void emitDocumentation(int Depth, const Documentation &Doc,
- const Record *DocInfo, raw_ostream &OS) {
- for (auto &O : Doc.Options)
- emitOption(O, DocInfo, OS);
- for (auto &G : Doc.Groups)
- emitGroup(Depth, G, DocInfo, OS);
- }
- } // namespace
- void clang::EmitClangOptDocs(RecordKeeper &Records, raw_ostream &OS) {
- const Record *DocInfo = Records.getDef("GlobalDocumentation");
- if (!DocInfo) {
- PrintFatalError("The GlobalDocumentation top-level definition is missing, "
- "no documentation will be generated.");
- return;
- }
- OS << DocInfo->getValueAsString("Intro") << "\n";
- OS << ".. program:: " << DocInfo->getValueAsString("Program") << "\n";
- emitDocumentation(0, extractDocumentation(Records), DocInfo, OS);
- }
|