|  | //===- 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 |