diff options
Diffstat (limited to 'utils/TableGen/ClangOptionDocEmitter.cpp')
-rw-r--r-- | utils/TableGen/ClangOptionDocEmitter.cpp | 391 |
1 files changed, 391 insertions, 0 deletions
diff --git a/utils/TableGen/ClangOptionDocEmitter.cpp b/utils/TableGen/ClangOptionDocEmitter.cpp new file mode 100644 index 0000000000000..aa7502e2c8506 --- /dev/null +++ b/utils/TableGen/ClangOptionDocEmitter.cpp @@ -0,0 +1,391 @@ +//===- ClangOptionDocEmitter.cpp - Documentation for command line flags ---===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +// FIXME: Once this has stabilized, consider moving it to LLVM. +// +//===----------------------------------------------------------------------===// + +#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 clang { +namespace docs { +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[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. + auto Name = 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]; + std::sort(A.begin(), A.end(), CompareByName); + return {R, std::move(A)}; + }; + + std::function<Documentation(Record *)> DocumentationForGroup = + [&](Record *R) -> Documentation { + Documentation D; + + auto &Groups = GroupsInGroup[R]; + std::sort(Groups.begin(), Groups.end(), 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]; + std::sort(Options.begin(), Options.end(), 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 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(); + else if (auto *CV = dyn_cast_or_null<CodeInit>(V->getValue())) + Value = CV->getValue(); + if (!Value.empty()) + return Field == Primary ? Value.str() : escapeRST(Value); + } + } + return StringRef(); +} + +void emitOptionWithArgs(StringRef Prefix, const Record *Option, + ArrayRef<std::string> 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; + } +} + +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); + + std::vector<std::string> Args; + if (!Option->isValueUnset("MetaVarName")) + Args.push_back(Option->getValueAsString("MetaVarName")); + else if (NumArgs == 1) + Args.push_back("<arg>"); + + 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, Args, OS); + + auto AliasArgs = Option->getValueAsListOfStrings("AliasArgs"); + if (!AliasArgs.empty()) { + Record *Alias = Option->getValueAsDef("Alias"); + OS << " (equivalent to "; + emitOptionWithArgs(Alias->getValueAsListOfStrings("Prefixes").front(), + Alias, Option->getValueAsListOfStrings("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 (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( + getSphinxOptionID(Prefix + Option->getValueAsString("Name"))); + }); + 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. + std::string Description = + getRSTStringWithTextFallback(Option.Option, "DocBrief", "HelpText"); + 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; + + 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 +} // namespace docs + +void EmitClangOptDocs(RecordKeeper &Records, raw_ostream &OS) { + using namespace docs; + + 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); +} +} // end namespace clang |