//===- 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 #include #include using namespace llvm; namespace clang { namespace docs { namespace { struct DocumentedOption { Record *Option; std::vector Aliases; }; struct DocumentedGroup; struct Documentation { std::vector Groups; std::vector 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 > OptionsInGroup; std::map > GroupsInGroup; std::map > Aliases; std::map 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(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(R->getValueInit("Group"))) Group = SkipFlattened(G->getDef()); GroupsInGroup[Group].push_back(R); } for (Record *R : Records.getAllDerivedDefinitions("Option")) { if (auto *A = dyn_cast(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 = 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(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 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 getSeparatorsForKind(const Record *OptionKind) { return StringSwitch>(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(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(V->getValue())) Value = SV->getValue(); else if (auto *CV = dyn_cast_or_null(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 Args, raw_ostream &OS) { OS << Prefix << escapeRST(Option->getValueAsString("Name")); std::pair 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 Args; if (!Option->isValueUnset("MetaVarName")) Args.push_back(Option->getValueAsString("MetaVarName")); else if (NumArgs == 1) Args.push_back(""); while (Args.size() < NumArgs) { Args.push_back(("").str()); // Use '--args ...' if any number of args are allowed. if (Args.size() == 2 && NumArgs == UnlimitedArgs) { Args.back() += "..."; break; } } emitOptionWithArgs(Prefix, Option, std::vector(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 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 SphinxOptionIDs; forEachOptionName(Option, DocInfo, [&](const Record *Option) { for (auto &Prefix : Option->getValueAsListOfStrings("Prefixes")) SphinxOptionIDs.push_back( getSphinxOptionID((Prefix + Option->getValueAsString("Name")).str())); }); assert(!SphinxOptionIDs.empty() && "no flags for option"); static std::map 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