diff options
Diffstat (limited to 'utils')
-rw-r--r-- | utils/TableGen/CMakeLists.txt | 1 | ||||
-rw-r--r-- | utils/TableGen/ClangAttrEmitter.cpp | 86 | ||||
-rw-r--r-- | utils/TableGen/ClangOptionDocEmitter.cpp | 391 | ||||
-rw-r--r-- | utils/TableGen/TableGen.cpp | 10 | ||||
-rw-r--r-- | utils/TableGen/TableGenBackends.h | 1 |
5 files changed, 450 insertions, 39 deletions
diff --git a/utils/TableGen/CMakeLists.txt b/utils/TableGen/CMakeLists.txt index 29a1eedb7af8b..c8e9537cb56bb 100644 --- a/utils/TableGen/CMakeLists.txt +++ b/utils/TableGen/CMakeLists.txt @@ -7,6 +7,7 @@ add_tablegen(clang-tblgen CLANG ClangCommentHTMLNamedCharacterReferenceEmitter.cpp ClangCommentHTMLTagsEmitter.cpp ClangDiagnosticsEmitter.cpp + ClangOptionDocEmitter.cpp ClangSACheckersEmitter.cpp NeonEmitter.cpp TableGen.cpp diff --git a/utils/TableGen/ClangAttrEmitter.cpp b/utils/TableGen/ClangAttrEmitter.cpp index 27ab34c1309d0..8aaa28beaac2a 100644 --- a/utils/TableGen/ClangAttrEmitter.cpp +++ b/utils/TableGen/ClangAttrEmitter.cpp @@ -90,13 +90,13 @@ GetFlattenedSpellings(const Record &Attr) { static std::string ReadPCHRecord(StringRef type) { return StringSwitch<std::string>(type) - .EndsWith("Decl *", "GetLocalDeclAs<" - + std::string(type, 0, type.size()-1) + ">(F, Record[Idx++])") - .Case("TypeSourceInfo *", "GetTypeSourceInfo(F, Record, Idx)") - .Case("Expr *", "ReadExpr(F)") - .Case("IdentifierInfo *", "GetIdentifierInfo(F, Record, Idx)") - .Case("StringRef", "ReadString(Record, Idx)") - .Default("Record[Idx++]"); + .EndsWith("Decl *", "Record.GetLocalDeclAs<" + + std::string(type, 0, type.size()-1) + ">(Record.readInt())") + .Case("TypeSourceInfo *", "Record.getTypeSourceInfo()") + .Case("Expr *", "Record.readExpr()") + .Case("IdentifierInfo *", "Record.getIdentifierInfo()") + .Case("StringRef", "Record.readString()") + .Default("Record.readInt()"); } // Get a type that is suitable for storing an object of the specified type. @@ -413,7 +413,7 @@ namespace { void writePCHReadDecls(raw_ostream &OS) const override { OS << " std::string " << getLowerName() - << "= ReadString(Record, Idx);\n"; + << "= Record.readString();\n"; } void writePCHReadArgs(raw_ostream &OS) const override { @@ -539,13 +539,13 @@ namespace { } void writePCHReadDecls(raw_ostream &OS) const override { - OS << " bool is" << getLowerName() << "Expr = Record[Idx++];\n"; + OS << " bool is" << getLowerName() << "Expr = Record.readInt();\n"; OS << " void *" << getLowerName() << "Ptr;\n"; OS << " if (is" << getLowerName() << "Expr)\n"; - OS << " " << getLowerName() << "Ptr = ReadExpr(F);\n"; + OS << " " << getLowerName() << "Ptr = Record.readExpr();\n"; OS << " else\n"; OS << " " << getLowerName() - << "Ptr = GetTypeSourceInfo(F, Record, Idx);\n"; + << "Ptr = Record.getTypeSourceInfo();\n"; } void writePCHWrite(raw_ostream &OS) const override { @@ -658,7 +658,7 @@ namespace { } void writePCHReadDecls(raw_ostream &OS) const override { - OS << " unsigned " << getLowerName() << "Size = Record[Idx++];\n"; + OS << " unsigned " << getLowerName() << "Size = Record.readInt();\n"; OS << " SmallVector<" << getType() << ", 4> " << getLowerName() << ";\n"; OS << " " << getLowerName() << ".reserve(" << getLowerName() @@ -783,7 +783,7 @@ namespace { void writePCHReadDecls(raw_ostream &OS) const override { OS << " " << getAttrName() << "Attr::" << type << " " << getLowerName() << "(static_cast<" << getAttrName() << "Attr::" << type - << ">(Record[Idx++]));\n"; + << ">(Record.readInt()));\n"; } void writePCHReadArgs(raw_ostream &OS) const override { @@ -906,14 +906,14 @@ namespace { } void writePCHReadDecls(raw_ostream &OS) const override { - OS << " unsigned " << getLowerName() << "Size = Record[Idx++];\n"; + OS << " unsigned " << getLowerName() << "Size = Record.readInt();\n"; OS << " SmallVector<" << QualifiedTypeName << ", 4> " << getLowerName() << ";\n"; OS << " " << getLowerName() << ".reserve(" << getLowerName() << "Size);\n"; OS << " for (unsigned i = " << getLowerName() << "Size; i; --i)\n"; OS << " " << getLowerName() << ".push_back(" << "static_cast<" - << QualifiedTypeName << ">(Record[Idx++]));\n"; + << QualifiedTypeName << ">(Record.readInt()));\n"; } void writePCHWrite(raw_ostream &OS) const override { @@ -996,7 +996,7 @@ namespace { void writePCHReadDecls(raw_ostream &OS) const override { OS << " VersionTuple " << getLowerName() - << "= ReadVersionTuple(Record, Idx);\n"; + << "= Record.readVersionTuple();\n"; } void writePCHReadArgs(raw_ostream &OS) const override { @@ -1036,7 +1036,7 @@ namespace { OS << " " << getType() << " tempInst" << getUpperName() << ";\n"; OS << " {\n"; OS << " EnterExpressionEvaluationContext " - << "Unevaluated(S, Sema::Unevaluated);\n"; + << "Unevaluated(S, Sema::ExpressionEvaluationContext::Unevaluated);\n"; OS << " ExprResult " << "Result = S.SubstExpr(" << "A->get" << getUpperName() << "(), TemplateArgs);\n"; OS << " tempInst" << getUpperName() << " = " @@ -1083,7 +1083,7 @@ namespace { << "[A->" << getLowerName() << "_size()];\n"; OS << " {\n"; OS << " EnterExpressionEvaluationContext " - << "Unevaluated(S, Sema::Unevaluated);\n"; + << "Unevaluated(S, Sema::ExpressionEvaluationContext::Unevaluated);\n"; OS << " " << getType() << " *TI = tempInst" << getUpperName() << ";\n"; OS << " " << getType() << " *I = A->" << getLowerName() @@ -2126,9 +2126,9 @@ void EmitClangAttrPCHRead(RecordKeeper &Records, raw_ostream &OS) { OS << " case attr::" << R.getName() << ": {\n"; if (R.isSubClassOf(InhClass)) - OS << " bool isInherited = Record[Idx++];\n"; - OS << " bool isImplicit = Record[Idx++];\n"; - OS << " unsigned Spelling = Record[Idx++];\n"; + OS << " bool isInherited = Record.readInt();\n"; + OS << " bool isImplicit = Record.readInt();\n"; + OS << " unsigned Spelling = Record.readInt();\n"; ArgRecords = R.getValueAsListOfDefs("Args"); Args.clear(); for (const auto *Arg : ArgRecords) { @@ -2451,26 +2451,19 @@ void EmitClangAttrASTVisitor(RecordKeeper &Records, raw_ostream &OS) { OS << "#endif // ATTR_VISITOR_DECLS_ONLY\n"; } -// Emits code to instantiate dependent attributes on templates. -void EmitClangAttrTemplateInstantiate(RecordKeeper &Records, raw_ostream &OS) { - emitSourceFileHeader("Template instantiation code for attributes", OS); - - std::vector<Record*> Attrs = Records.getAllDerivedDefinitions("Attr"); - - OS << "namespace clang {\n" - << "namespace sema {\n\n" - << "Attr *instantiateTemplateAttribute(const Attr *At, ASTContext &C, " - << "Sema &S,\n" - << " const MultiLevelTemplateArgumentList &TemplateArgs) {\n" - << " switch (At->getKind()) {\n"; +void EmitClangAttrTemplateInstantiateHelper(const std::vector<Record *> &Attrs, + raw_ostream &OS, + bool AppliesToDecl) { + OS << " switch (At->getKind()) {\n"; for (const auto *Attr : Attrs) { const Record &R = *Attr; if (!R.getValueAsBit("ASTNode")) continue; - OS << " case attr::" << R.getName() << ": {\n"; - bool ShouldClone = R.getValueAsBit("Clone"); + bool ShouldClone = R.getValueAsBit("Clone") && + (!AppliesToDecl || + R.getValueAsBit("MeaningfulToClassTemplateDefinition")); if (!ShouldClone) { OS << " return nullptr;\n"; @@ -2507,8 +2500,27 @@ void EmitClangAttrTemplateInstantiate(RecordKeeper &Records, raw_ostream &OS) { } OS << " } // end switch\n" << " llvm_unreachable(\"Unknown attribute!\");\n" - << " return nullptr;\n" - << "}\n\n" + << " return nullptr;\n"; +} + +// Emits code to instantiate dependent attributes on templates. +void EmitClangAttrTemplateInstantiate(RecordKeeper &Records, raw_ostream &OS) { + emitSourceFileHeader("Template instantiation code for attributes", OS); + + std::vector<Record*> Attrs = Records.getAllDerivedDefinitions("Attr"); + + OS << "namespace clang {\n" + << "namespace sema {\n\n" + << "Attr *instantiateTemplateAttribute(const Attr *At, ASTContext &C, " + << "Sema &S,\n" + << " const MultiLevelTemplateArgumentList &TemplateArgs) {\n"; + EmitClangAttrTemplateInstantiateHelper(Attrs, OS, /*AppliesToDecl*/false); + OS << "}\n\n" + << "Attr *instantiateTemplateAttributeForDecl(const Attr *At,\n" + << " ASTContext &C, Sema &S,\n" + << " const MultiLevelTemplateArgumentList &TemplateArgs) {\n"; + EmitClangAttrTemplateInstantiateHelper(Attrs, OS, /*AppliesToDecl*/true); + OS << "}\n\n" << "} // end namespace sema\n" << "} // end namespace clang\n"; } 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 diff --git a/utils/TableGen/TableGen.cpp b/utils/TableGen/TableGen.cpp index 6fb5b00c4bac5..fd7999be38774 100644 --- a/utils/TableGen/TableGen.cpp +++ b/utils/TableGen/TableGen.cpp @@ -53,7 +53,8 @@ enum ActionType { GenArmNeonSema, GenArmNeonTest, GenAttrDocs, - GenDiagDocs + GenDiagDocs, + GenOptDocs }; namespace { @@ -135,7 +136,9 @@ cl::opt<ActionType> Action( clEnumValN(GenAttrDocs, "gen-attr-docs", "Generate attribute documentation"), clEnumValN(GenDiagDocs, "gen-diag-docs", - "Generate attribute documentation"))); + "Generate diagnostic documentation"), + clEnumValN(GenOptDocs, "gen-opt-docs", + "Generate option documentation"))); cl::opt<std::string> ClangComponent("clang-component", @@ -238,6 +241,9 @@ bool ClangTableGenMain(raw_ostream &OS, RecordKeeper &Records) { case GenDiagDocs: EmitClangDiagDocs(Records, OS); break; + case GenOptDocs: + EmitClangOptDocs(Records, OS); + break; } return false; diff --git a/utils/TableGen/TableGenBackends.h b/utils/TableGen/TableGenBackends.h index 0305ed1c8c8f5..033cb78f36f36 100644 --- a/utils/TableGen/TableGenBackends.h +++ b/utils/TableGen/TableGenBackends.h @@ -70,6 +70,7 @@ void EmitNeonTest2(RecordKeeper &Records, raw_ostream &OS); void EmitClangAttrDocs(RecordKeeper &Records, raw_ostream &OS); void EmitClangDiagDocs(RecordKeeper &Records, raw_ostream &OS); +void EmitClangOptDocs(RecordKeeper &Records, raw_ostream &OS); } // end namespace clang |