diff --git a/clang-tools-extra/clangd/CompileCommands.cpp b/clang-tools-extra/clangd/CompileCommands.cpp index c9da98e96ccfb..c1be93730129a 100644 --- a/clang-tools-extra/clangd/CompileCommands.cpp +++ b/clang-tools-extra/clangd/CompileCommands.cpp @@ -466,7 +466,7 @@ llvm::ArrayRef ArgStripper::rulesFor(llvm::StringRef Arg) { } AliasTable[] = { #define OPTION(PREFIX, PREFIXED_NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, \ FLAGS, VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, \ - METAVAR, VALUES) \ + METAVAR, VALUES, SUBCOMMANDIDS_OFFSET) \ {DriverID::OPT_##ID, DriverID::OPT_##ALIAS, ALIASARGS}, #include "clang/Driver/Options.inc" #undef OPTION diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp index 422375240bab6..ef39e85191352 100644 --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -533,9 +533,9 @@ static T extractMaskValue(T KeyPath) { #define PARSE_OPTION_WITH_MARSHALLING( \ ARGS, DIAGS, PREFIX_TYPE, SPELLING_OFFSET, ID, KIND, GROUP, ALIAS, \ ALIASARGS, FLAGS, VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, \ - METAVAR, VALUES, SHOULD_PARSE, ALWAYS_EMIT, KEYPATH, DEFAULT_VALUE, \ - IMPLIED_CHECK, IMPLIED_VALUE, NORMALIZER, DENORMALIZER, MERGER, EXTRACTOR, \ - TABLE_INDEX) \ + METAVAR, VALUES, SUBCOMMANDIDS_OFFSET, SHOULD_PARSE, ALWAYS_EMIT, KEYPATH, \ + DEFAULT_VALUE, IMPLIED_CHECK, IMPLIED_VALUE, NORMALIZER, DENORMALIZER, \ + MERGER, EXTRACTOR, TABLE_INDEX) \ if ((VISIBILITY) & options::CC1Option) { \ KEYPATH = MERGER(KEYPATH, DEFAULT_VALUE); \ if (IMPLIED_CHECK) \ @@ -551,8 +551,9 @@ static T extractMaskValue(T KeyPath) { #define GENERATE_OPTION_WITH_MARSHALLING( \ CONSUMER, PREFIX_TYPE, SPELLING_OFFSET, ID, KIND, GROUP, ALIAS, ALIASARGS, \ FLAGS, VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, METAVAR, VALUES, \ - SHOULD_PARSE, ALWAYS_EMIT, KEYPATH, DEFAULT_VALUE, IMPLIED_CHECK, \ - IMPLIED_VALUE, NORMALIZER, DENORMALIZER, MERGER, EXTRACTOR, TABLE_INDEX) \ + SUBCOMMANDIDS_OFFSET, SHOULD_PARSE, ALWAYS_EMIT, KEYPATH, DEFAULT_VALUE, \ + IMPLIED_CHECK, IMPLIED_VALUE, NORMALIZER, DENORMALIZER, MERGER, EXTRACTOR, \ + TABLE_INDEX) \ if ((VISIBILITY) & options::CC1Option) { \ [&](const auto &Extracted) { \ if (ALWAYS_EMIT || \ diff --git a/clang/tools/clang-installapi/Options.h b/clang/tools/clang-installapi/Options.h index d62f2efd3141a..f48459468372a 100644 --- a/clang/tools/clang-installapi/Options.h +++ b/clang/tools/clang-installapi/Options.h @@ -208,7 +208,7 @@ enum ID { OPT_INVALID = 0, // This is not an option ID. #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, \ VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, METAVAR, \ - VALUES) \ + VALUES, SUBCOMMANDIDS_OFFSET) \ OPT_##ID, #include "InstallAPIOpts.inc" LastOption diff --git a/lld/MachO/DriverUtils.cpp b/lld/MachO/DriverUtils.cpp index a3b722f13daca..3ff9d96ed53ce 100644 --- a/lld/MachO/DriverUtils.cpp +++ b/lld/MachO/DriverUtils.cpp @@ -45,7 +45,7 @@ using namespace lld::macho; static constexpr OptTable::Info optInfo[] = { #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, \ VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, METAVAR, \ - VALUES) \ + VALUES, SUBCOMMANDIDS_OFFSET) \ {PREFIX, \ NAME, \ HELPTEXT, \ @@ -59,7 +59,8 @@ static constexpr OptTable::Info optInfo[] = { OPT_##GROUP, \ OPT_##ALIAS, \ ALIASARGS, \ - VALUES}, + VALUES, \ + SUBCOMMANDIDS_OFFSET}, #include "Options.inc" #undef OPTION }; diff --git a/lld/MinGW/Driver.cpp b/lld/MinGW/Driver.cpp index 5098dbd77b4fd..1180097ce08cf 100644 --- a/lld/MinGW/Driver.cpp +++ b/lld/MinGW/Driver.cpp @@ -69,7 +69,7 @@ enum { static constexpr opt::OptTable::Info infoTable[] = { #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, \ VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, METAVAR, \ - VALUES) \ + VALUES, SUBCOMMANDIDS_OFFSET) \ {PREFIX, \ NAME, \ HELPTEXT, \ @@ -83,7 +83,8 @@ static constexpr opt::OptTable::Info infoTable[] = { OPT_##GROUP, \ OPT_##ALIAS, \ ALIASARGS, \ - VALUES}, + VALUES, \ + SUBCOMMANDIDS_OFFSET}, #include "Options.inc" #undef OPTION }; diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp index 46c848d5c1232..9c0e1b58e62f9 100644 --- a/lld/wasm/Driver.cpp +++ b/lld/wasm/Driver.cpp @@ -157,7 +157,7 @@ bool link(ArrayRef args, llvm::raw_ostream &stdoutOS, static constexpr opt::OptTable::Info optInfo[] = { #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, \ VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, METAVAR, \ - VALUES) \ + VALUES, SUBCOMMANDIDS_OFFSET) \ {PREFIX, \ NAME, \ HELPTEXT, \ @@ -171,7 +171,8 @@ static constexpr opt::OptTable::Info optInfo[] = { OPT_##GROUP, \ OPT_##ALIAS, \ ALIASARGS, \ - VALUES}, + VALUES, \ + SUBCOMMANDIDS_OFFSET}, #include "Options.inc" #undef OPTION }; diff --git a/llvm/examples/CMakeLists.txt b/llvm/examples/CMakeLists.txt index 74613bd1350bd..b10a94c5493b8 100644 --- a/llvm/examples/CMakeLists.txt +++ b/llvm/examples/CMakeLists.txt @@ -8,6 +8,7 @@ add_subdirectory(ModuleMaker) add_subdirectory(OrcV2Examples) add_subdirectory(SpeculativeJIT) add_subdirectory(Bye) +add_subdirectory(OptSubcommand) if(LLVM_ENABLE_EH AND (NOT WIN32) AND (NOT "${LLVM_NATIVE_ARCH}" STREQUAL "ARM")) add_subdirectory(ExceptionDemo) diff --git a/llvm/examples/OptSubcommand/CMakeLists.txt b/llvm/examples/OptSubcommand/CMakeLists.txt new file mode 100644 index 0000000000000..debc948611866 --- /dev/null +++ b/llvm/examples/OptSubcommand/CMakeLists.txt @@ -0,0 +1,19 @@ +# Set the .td file to be processed for this target. +set(LLVM_TARGET_DEFINITIONS Opts.td) + +tablegen(LLVM Opts.inc -gen-opt-parser-defs) +add_public_tablegen_target(HelloSubTableGen) + +set(LLVM_LINK_COMPONENTS + Support + Option + ) + +add_llvm_example(OptSubcommand + llvm-hello-sub.cpp + ) + +target_include_directories(OptSubcommand + PRIVATE + ${CMAKE_CURRENT_BINARY_DIR} + ) diff --git a/llvm/examples/OptSubcommand/Opts.td b/llvm/examples/OptSubcommand/Opts.td new file mode 100644 index 0000000000000..7c980ee7a0e7f --- /dev/null +++ b/llvm/examples/OptSubcommand/Opts.td @@ -0,0 +1,18 @@ +include "llvm/Option/OptParser.td" + +def sc_foo : SubCommand<"foo", "HelpText for SubCommand foo.">; + +def sc_bar : SubCommand<"bar", "HelpText for SubCommand bar.", + "OptSubcommand bar ">; + +def help : Flag<["--"], "help">, + HelpText<"OptSubcommand ">; + +def version : Flag<["-"], "version">, + HelpText<"Toplevel Display the version number">; + +def uppercase : Flag<["-"], "uppercase", [sc_foo, sc_bar]>, + HelpText<"Print in uppercase">; + +def lowercase : Flag<["-"], "lowercase", [sc_foo]>, + HelpText<"Print in lowercase">; diff --git a/llvm/examples/OptSubcommand/llvm-hello-sub.cpp b/llvm/examples/OptSubcommand/llvm-hello-sub.cpp new file mode 100644 index 0000000000000..8071f56cb3685 --- /dev/null +++ b/llvm/examples/OptSubcommand/llvm-hello-sub.cpp @@ -0,0 +1,137 @@ +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Option/ArgList.h" +#include "llvm/Option/OptTable.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/InitLLVM.h" +#include "llvm/Support/raw_ostream.h" + +using namespace llvm; +using namespace llvm::opt; + +namespace { +enum ID { + OPT_INVALID = 0, +#define OPTION(PREFIXES, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, \ + VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, METAVAR, \ + VALUES, SUBCOMMANDIDS_OFFSET) \ + OPT_##ID, +#include "Opts.inc" +#undef OPTION +}; +#define OPTTABLE_STR_TABLE_CODE +#include "Opts.inc" +#undef OPTTABLE_STR_TABLE_CODE + +#define OPTTABLE_PREFIXES_TABLE_CODE +#include "Opts.inc" +#undef OPTTABLE_PREFIXES_TABLE_CODE + +#define OPTTABLE_SUBCOMMAND_IDS_TABLE_CODE +#include "Opts.inc" +#undef OPTTABLE_SUBCOMMAND_IDS_TABLE_CODE + +#define OPTTABLE_SUBCOMMANDS_CODE +#include "Opts.inc" +#undef OPTTABLE_SUBCOMMANDS_CODE + +static constexpr OptTable::Info InfoTable[] = { +#define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__), +#include "Opts.inc" +#undef OPTION +}; + +class HelloSubOptTable : public GenericOptTable { +public: + HelloSubOptTable() + : GenericOptTable(OptionStrTable, OptionPrefixesTable, InfoTable, + /*IgnoreCase=*/false, OptionSubCommands, + OptionSubCommandIDsTable) {} +}; +} // namespace + +int main(int argc, char **argv) { + InitLLVM X(argc, argv); + HelloSubOptTable T; + unsigned MissingArgIndex, MissingArgCount; + + auto HandleMultipleSubcommands = [](ArrayRef SubCommands) { + assert(SubCommands.size() > 1); + llvm::errs() << "error: more than one subcommand passed [\n"; + for (auto SC : SubCommands) + llvm::errs() << " `" << SC << "`\n"; + llvm::errs() << "]\n"; + llvm::errs() << "See --help.\n"; + exit(1); + }; + + auto HandleOtherPositionals = [](ArrayRef Positionals) { + assert(!Positionals.empty()); + llvm::errs() << "error: unknown positional argument(s) [\n"; + for (auto SC : Positionals) + llvm::errs() << " `" << SC << "`\n"; + llvm::errs() << "]\n"; + llvm::errs() << "See --help.\n"; + exit(1); + }; + + InputArgList Args = T.ParseArgs(ArrayRef(argv + 1, argc - 1), MissingArgIndex, + MissingArgCount); + + StringRef SubCommand = Args.getSubCommand( + T.getSubCommands(), HandleMultipleSubcommands, HandleOtherPositionals); + // Handle help. When help options is found, ignore all other options and exit + // after printing help. + + if (Args.hasArg(OPT_help)) { + T.printHelp(llvm::outs(), "llvm-hello-sub [subcommand] [options]", + "LLVM Hello SubCommand Example", false, false, Visibility(), + SubCommand); + return 0; + } + + auto HandleSubCommandArg = [&](ID OptionType) { + if (!Args.hasArg(OptionType)) + return false; + auto O = T.getOption(OptionType); + if (!O.isRegisteredSC(SubCommand)) { + llvm::errs() << "Option [" << O.getName() + << "] is not valid for SubCommand [" << SubCommand << "]\n"; + return false; + } + return true; + }; + + bool HasUnknownOptions = false; + for (const Arg *A : Args.filtered(OPT_UNKNOWN)) { + HasUnknownOptions = true; + llvm::errs() << "Unknown option `" << A->getAsString(Args) << "'\n"; + } + if (HasUnknownOptions) { + llvm::errs() << "See `OptSubcommand --help`.\n"; + return 1; + } + if (SubCommand.empty()) { + if (Args.hasArg(OPT_version)) + llvm::outs() << "LLVM Hello SubCommand Example 1.0\n"; + } else if (SubCommand == "foo") { + if (HandleSubCommandArg(OPT_uppercase)) + llvm::outs() << "FOO\n"; + else if (HandleSubCommandArg(OPT_lowercase)) + llvm::outs() << "foo\n"; + + if (HandleSubCommandArg(OPT_version)) + llvm::outs() << "LLVM Hello SubCommand foo Example 1.0\n"; + + } else if (SubCommand == "bar") { + if (HandleSubCommandArg(OPT_lowercase)) + llvm::outs() << "bar\n"; + else if (HandleSubCommandArg(OPT_uppercase)) + llvm::outs() << "BAR\n"; + + if (HandleSubCommandArg(OPT_version)) + llvm::outs() << "LLVM Hello SubCommand bar Example 1.0\n"; + } + + return 0; +} diff --git a/llvm/include/llvm/Option/ArgList.h b/llvm/include/llvm/Option/ArgList.h index 3e80574355b87..db36509125d21 100644 --- a/llvm/include/llvm/Option/ArgList.h +++ b/llvm/include/llvm/Option/ArgList.h @@ -20,6 +20,7 @@ #include "llvm/Option/OptSpecifier.h" #include "llvm/Option/Option.h" #include "llvm/Support/Compiler.h" +#include "llvm/Support/Error.h" #include #include #include @@ -280,6 +281,22 @@ class ArgList { /// list. virtual unsigned getNumInputArgStrings() const = 0; + /// getSubCommand - Find subcommand from the arguments if the usage is valid. + /// + /// \param AllSubCommands - A list of all valid subcommands. + /// \param HandleMultipleSubcommands - A callback for the case where multiple + /// subcommands are present in the arguments. It gets a list of all found + /// subcommands. + /// \param HandleOtherPositionals - A callback for the case where positional + /// arguments that are not subcommands are present. + /// \return The name of the subcommand found. If no subcommand is found, + /// this returns an empty StringRef. If multiple subcommands are found, the + /// first one is returned. + StringRef getSubCommand( + ArrayRef AllSubCommands, + std::function)> HandleMultipleSubcommands, + std::function)> HandleOtherPositionals) const; + /// @} /// @name Argument Lookup Utilities /// @{ diff --git a/llvm/include/llvm/Option/OptParser.td b/llvm/include/llvm/Option/OptParser.td index 9fd606b0d6fcb..8f32fb4493511 100644 --- a/llvm/include/llvm/Option/OptParser.td +++ b/llvm/include/llvm/Option/OptParser.td @@ -98,7 +98,15 @@ class HelpTextVariant visibilities, string text> { string Text = text; } -class Option prefixes, string name, OptionKind kind> { +// Class definition for positional subcommands. +class SubCommand { + string Name = name; + string HelpText = helpText; + string Usage = usage; +} + +class Option prefixes, string name, OptionKind kind, + list subcommands = []> { string EnumName = ?; // Uses the def name if undefined. list Prefixes = prefixes; string Name = name; @@ -129,26 +137,34 @@ class Option prefixes, string name, OptionKind kind> { code ValueMerger = "mergeForwardValue"; code ValueExtractor = "extractForwardValue"; list NormalizedValues = ?; + list SubCommands = subcommands; } // Helpers for defining options. -class Flag prefixes, string name> - : Option; -class Joined prefixes, string name> - : Option; -class Separate prefixes, string name> - : Option; -class CommaJoined prefixes, string name> - : Option; -class MultiArg prefixes, string name, int numargs> - : Option { +class Flag prefixes, string name, + list subcommands = []> + : Option; +class Joined prefixes, string name, + list subcommands = []> + : Option; +class Separate prefixes, string name, + list subcommands = []> + : Option; +class CommaJoined prefixes, string name, + list subcommands = []> + : Option; +class MultiArg prefixes, string name, int numargs, + list subcommands = []> + : Option { int NumArgs = numargs; } -class JoinedOrSeparate prefixes, string name> - : Option; -class JoinedAndSeparate prefixes, string name> - : Option; +class JoinedOrSeparate prefixes, string name, + list subcommands = []> + : Option; +class JoinedAndSeparate prefixes, string name, + list subcommands = []> + : Option; // Mix-ins for adding optional attributes. diff --git a/llvm/include/llvm/Option/OptTable.h b/llvm/include/llvm/Option/OptTable.h index df42ee341ee58..f641ca4ac08d3 100644 --- a/llvm/include/llvm/Option/OptTable.h +++ b/llvm/include/llvm/Option/OptTable.h @@ -53,6 +53,13 @@ class Visibility { /// parts of the driver still use Option instances where convenient. class LLVM_ABI OptTable { public: + /// Represents a subcommand and its options in the option table. + struct SubCommand { + const char *Name; + const char *HelpText; + const char *Usage; + }; + /// Entry for a single option instance in the option data table. struct Info { unsigned PrefixesOffset; @@ -79,6 +86,8 @@ class LLVM_ABI OptTable { unsigned short AliasID; const char *AliasArgs; const char *Values; + // Offset into OptTable's SubCommandIDsTable. + unsigned SubCommandIDsOffset; bool hasNoPrefix() const { return PrefixesOffset == 0; } @@ -94,6 +103,21 @@ class LLVM_ABI OptTable { getNumPrefixes(PrefixesTable)); } + bool hasSubCommands() const { return SubCommandIDsOffset != 0; } + + unsigned getNumSubCommandIDs(ArrayRef SubCommandIDsTable) const { + // We embed the number of subcommand IDs in the value of the first offset. + return SubCommandIDsTable[SubCommandIDsOffset]; + } + + ArrayRef + getSubCommandIDs(ArrayRef SubCommandIDsTable) const { + return hasSubCommands() ? SubCommandIDsTable.slice( + SubCommandIDsOffset + 1, + getNumSubCommandIDs(SubCommandIDsTable)) + : ArrayRef(); + } + void appendPrefixes(const StringTable &StrTable, ArrayRef PrefixesTable, SmallVectorImpl &Prefixes) const { @@ -119,6 +143,22 @@ class LLVM_ABI OptTable { } }; +public: + bool isValidForSubCommand(const Info *CandidateInfo, + StringRef SubCommand) const { + assert(!SubCommand.empty() && + "This helper is only for valid registered subcommands."); + auto SCIT = + std::find_if(SubCommands.begin(), SubCommands.end(), + [&](const auto &C) { return SubCommand == C.Name; }); + assert(SCIT != SubCommands.end() && + "This helper is only for valid registered subcommands."); + auto SubCommandIDs = CandidateInfo->getSubCommandIDs(SubCommandIDsTable); + unsigned CurrentSubCommandID = SCIT - &SubCommands[0]; + return std::find(SubCommandIDs.begin(), SubCommandIDs.end(), + CurrentSubCommandID) != SubCommandIDs.end(); + } + private: // A unified string table for these options. Individual strings are stored as // null terminated C-strings at offsets within this table. @@ -134,6 +174,13 @@ class LLVM_ABI OptTable { ArrayRef OptionInfos; bool IgnoreCase; + + /// The subcommand information table. + ArrayRef SubCommands; + + /// The subcommand IDs table. + ArrayRef SubCommandIDsTable; + bool GroupedShortOptions = false; bool DashDashParsing = false; const char *EnvVar = nullptr; @@ -168,7 +215,9 @@ class LLVM_ABI OptTable { /// manually call \c buildPrefixChars once they are fully constructed. OptTable(const StringTable &StrTable, ArrayRef PrefixesTable, - ArrayRef OptionInfos, bool IgnoreCase = false); + ArrayRef OptionInfos, bool IgnoreCase = false, + ArrayRef SubCommands = {}, + ArrayRef SubCommandIDsTable = {}); /// Build (or rebuild) the PrefixChars member. void buildPrefixChars(); @@ -179,6 +228,8 @@ class LLVM_ABI OptTable { /// Return the string table used for option names. const StringTable &getStrTable() const { return *StrTable; } + ArrayRef getSubCommands() const { return SubCommands; } + /// Return the prefixes table used for option names. ArrayRef getPrefixesTable() const { return PrefixesTable; @@ -410,7 +461,8 @@ class LLVM_ABI OptTable { /// texts. void printHelp(raw_ostream &OS, const char *Usage, const char *Title, bool ShowHidden = false, bool ShowAllAliases = false, - Visibility VisibilityMask = Visibility()) const; + Visibility VisibilityMask = Visibility(), + StringRef SubCommand = {}) const; void printHelp(raw_ostream &OS, const char *Usage, const char *Title, unsigned FlagsToInclude, unsigned FlagsToExclude, @@ -418,7 +470,8 @@ class LLVM_ABI OptTable { private: void internalPrintHelp(raw_ostream &OS, const char *Usage, const char *Title, - bool ShowHidden, bool ShowAllAliases, + StringRef SubCommand, bool ShowHidden, + bool ShowAllAliases, std::function ExcludeOption, Visibility VisibilityMask) const; }; @@ -428,7 +481,9 @@ class GenericOptTable : public OptTable { protected: LLVM_ABI GenericOptTable(const StringTable &StrTable, ArrayRef PrefixesTable, - ArrayRef OptionInfos, bool IgnoreCase = false); + ArrayRef OptionInfos, bool IgnoreCase = false, + ArrayRef SubCommands = {}, + ArrayRef SubCommandIDsTable = {}); }; class PrecomputedOptTable : public OptTable { @@ -437,8 +492,11 @@ class PrecomputedOptTable : public OptTable { ArrayRef PrefixesTable, ArrayRef OptionInfos, ArrayRef PrefixesUnionOffsets, - bool IgnoreCase = false) - : OptTable(StrTable, PrefixesTable, OptionInfos, IgnoreCase) { + bool IgnoreCase = false, + ArrayRef SubCommands = {}, + ArrayRef SubCommandIDsTable = {}) + : OptTable(StrTable, PrefixesTable, OptionInfos, IgnoreCase, SubCommands, + SubCommandIDsTable) { for (auto PrefixOffset : PrefixesUnionOffsets) PrefixesUnion.push_back(StrTable[PrefixOffset]); buildPrefixChars(); @@ -452,33 +510,36 @@ class PrecomputedOptTable : public OptTable { #define LLVM_MAKE_OPT_ID_WITH_ID_PREFIX( \ ID_PREFIX, PREFIXES_OFFSET, PREFIXED_NAME_OFFSET, ID, KIND, GROUP, ALIAS, \ ALIASARGS, FLAGS, VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, \ - METAVAR, VALUES) \ + METAVAR, VALUES, SUBCOMMANDIDS_OFFSET) \ ID_PREFIX##ID #define LLVM_MAKE_OPT_ID(PREFIXES_OFFSET, PREFIXED_NAME_OFFSET, ID, KIND, \ GROUP, ALIAS, ALIASARGS, FLAGS, VISIBILITY, PARAM, \ - HELPTEXT, HELPTEXTSFORVARIANTS, METAVAR, VALUES) \ - LLVM_MAKE_OPT_ID_WITH_ID_PREFIX(OPT_, PREFIXES_OFFSET, PREFIXED_NAME_OFFSET, \ - ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, \ - VISIBILITY, PARAM, HELPTEXT, \ - HELPTEXTSFORVARIANTS, METAVAR, VALUES) + HELPTEXT, HELPTEXTSFORVARIANTS, METAVAR, VALUES, \ + SUBCOMMANDIDS_OFFSET) \ + LLVM_MAKE_OPT_ID_WITH_ID_PREFIX( \ + OPT_, PREFIXES_OFFSET, PREFIXED_NAME_OFFSET, ID, KIND, GROUP, ALIAS, \ + ALIASARGS, FLAGS, VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, \ + METAVAR, VALUES, SUBCOMMANDIDS_OFFSET) #define LLVM_CONSTRUCT_OPT_INFO_WITH_ID_PREFIX( \ ID_PREFIX, PREFIXES_OFFSET, PREFIXED_NAME_OFFSET, ID, KIND, GROUP, ALIAS, \ ALIASARGS, FLAGS, VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, \ - METAVAR, VALUES) \ + METAVAR, VALUES, SUBCOMMANDIDS_OFFSET) \ llvm::opt::OptTable::Info { \ PREFIXES_OFFSET, PREFIXED_NAME_OFFSET, HELPTEXT, HELPTEXTSFORVARIANTS, \ METAVAR, ID_PREFIX##ID, llvm::opt::Option::KIND##Class, PARAM, FLAGS, \ - VISIBILITY, ID_PREFIX##GROUP, ID_PREFIX##ALIAS, ALIASARGS, VALUES \ + VISIBILITY, ID_PREFIX##GROUP, ID_PREFIX##ALIAS, ALIASARGS, VALUES, \ + SUBCOMMANDIDS_OFFSET \ } #define LLVM_CONSTRUCT_OPT_INFO( \ PREFIXES_OFFSET, PREFIXED_NAME_OFFSET, ID, KIND, GROUP, ALIAS, ALIASARGS, \ - FLAGS, VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, METAVAR, VALUES) \ + FLAGS, VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, METAVAR, VALUES, \ + SUBCOMMANDIDS_OFFSET) \ LLVM_CONSTRUCT_OPT_INFO_WITH_ID_PREFIX( \ OPT_, PREFIXES_OFFSET, PREFIXED_NAME_OFFSET, ID, KIND, GROUP, ALIAS, \ ALIASARGS, FLAGS, VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, \ - METAVAR, VALUES) + METAVAR, VALUES, SUBCOMMANDIDS_OFFSET) #endif // LLVM_OPTION_OPTTABLE_H diff --git a/llvm/include/llvm/Option/Option.h b/llvm/include/llvm/Option/Option.h index 51c330a90813c..192cb3c96700a 100644 --- a/llvm/include/llvm/Option/Option.h +++ b/llvm/include/llvm/Option/Option.h @@ -216,6 +216,12 @@ class Option { /// always be false. LLVM_ABI bool matches(OptSpecifier ID) const; + LLVM_ABI bool isRegisteredSC(StringRef SubCommand) const { + assert(Info && "Must have a valid info!"); + assert(Owner && "Must have a valid owner!"); + return Owner->isValidForSubCommand(Info, SubCommand); + } + /// Potentially accept the current argument, returning a new Arg instance, /// or 0 if the option does not accept this argument (or the argument is /// missing values). diff --git a/llvm/lib/Option/ArgList.cpp b/llvm/lib/Option/ArgList.cpp index c4188b3b12112..2f4e21257af09 100644 --- a/llvm/lib/Option/ArgList.cpp +++ b/llvm/lib/Option/ArgList.cpp @@ -14,12 +14,14 @@ #include "llvm/Config/llvm-config.h" #include "llvm/Option/Arg.h" #include "llvm/Option/OptSpecifier.h" +#include "llvm/Option/OptTable.h" #include "llvm/Option/Option.h" #include "llvm/Support/Compiler.h" #include "llvm/Support/Debug.h" #include "llvm/Support/raw_ostream.h" #include #include +#include #include #include #include @@ -202,6 +204,42 @@ void ArgList::print(raw_ostream &O) const { LLVM_DUMP_METHOD void ArgList::dump() const { print(dbgs()); } #endif +StringRef ArgList::getSubCommand( + ArrayRef AllSubCommands, + std::function)> HandleMultipleSubcommands, + std::function)> HandleOtherPositionals) const { + + SmallVector SubCommands; + SmallVector OtherPositionals; + for (const Arg *A : *this) { + if (A->getOption().getKind() != Option::InputClass) + continue; + + size_t OldSize = SubCommands.size(); + for (const OptTable::SubCommand &CMD : AllSubCommands) { + if (StringRef(CMD.Name) == A->getValue()) + SubCommands.push_back(A->getValue()); + } + + if (SubCommands.size() == OldSize) + OtherPositionals.push_back(A->getValue()); + } + + // Invoke callbacks if necessary. + if (SubCommands.size() > 1) { + HandleMultipleSubcommands(SubCommands); + return {}; + } + if (!OtherPositionals.empty()) { + HandleOtherPositionals(OtherPositionals); + return {}; + } + + if (SubCommands.size() == 1) + return SubCommands.front(); + return {}; // No valid usage of subcommand found. +} + void InputArgList::releaseMemory() { // An InputArgList always owns its arguments. for (Arg *A : *this) diff --git a/llvm/lib/Option/OptTable.cpp b/llvm/lib/Option/OptTable.cpp index 6d10e6154147e..14e3b0d60886d 100644 --- a/llvm/lib/Option/OptTable.cpp +++ b/llvm/lib/Option/OptTable.cpp @@ -79,9 +79,12 @@ OptSpecifier::OptSpecifier(const Option *Opt) : ID(Opt->getID()) {} OptTable::OptTable(const StringTable &StrTable, ArrayRef PrefixesTable, - ArrayRef OptionInfos, bool IgnoreCase) + ArrayRef OptionInfos, bool IgnoreCase, + ArrayRef SubCommands, + ArrayRef SubCommandIDsTable) : StrTable(&StrTable), PrefixesTable(PrefixesTable), - OptionInfos(OptionInfos), IgnoreCase(IgnoreCase) { + OptionInfos(OptionInfos), IgnoreCase(IgnoreCase), + SubCommands(SubCommands), SubCommandIDsTable(SubCommandIDsTable) { // Explicitly zero initialize the error to work around a bug in array // value-initialization on MinGW with gcc 4.3.5. @@ -715,9 +718,10 @@ static const char *getOptionHelpGroup(const OptTable &Opts, OptSpecifier Id) { void OptTable::printHelp(raw_ostream &OS, const char *Usage, const char *Title, bool ShowHidden, bool ShowAllAliases, - Visibility VisibilityMask) const { + Visibility VisibilityMask, + StringRef SubCommand) const { return internalPrintHelp( - OS, Usage, Title, ShowHidden, ShowAllAliases, + OS, Usage, Title, SubCommand, ShowHidden, ShowAllAliases, [VisibilityMask](const Info &CandidateInfo) -> bool { return (CandidateInfo.Visibility & VisibilityMask) == 0; }, @@ -730,7 +734,7 @@ void OptTable::printHelp(raw_ostream &OS, const char *Usage, const char *Title, bool ShowHidden = !(FlagsToExclude & HelpHidden); FlagsToExclude &= ~HelpHidden; return internalPrintHelp( - OS, Usage, Title, ShowHidden, ShowAllAliases, + OS, Usage, Title, /*SubCommand=*/{}, ShowHidden, ShowAllAliases, [FlagsToInclude, FlagsToExclude](const Info &CandidateInfo) { if (FlagsToInclude && !(CandidateInfo.Flags & FlagsToInclude)) return true; @@ -742,16 +746,62 @@ void OptTable::printHelp(raw_ostream &OS, const char *Usage, const char *Title, } void OptTable::internalPrintHelp( - raw_ostream &OS, const char *Usage, const char *Title, bool ShowHidden, - bool ShowAllAliases, std::function ExcludeOption, + raw_ostream &OS, const char *Usage, const char *Title, StringRef SubCommand, + bool ShowHidden, bool ShowAllAliases, + std::function ExcludeOption, Visibility VisibilityMask) const { OS << "OVERVIEW: " << Title << "\n\n"; - OS << "USAGE: " << Usage << "\n\n"; // Render help text into a map of group-name to a list of (option, help) // pairs. std::map> GroupedOptionHelp; + auto ActiveSubCommand = + std::find_if(SubCommands.begin(), SubCommands.end(), + [&](const auto &C) { return SubCommand == C.Name; }); + if (!SubCommand.empty()) { + assert(ActiveSubCommand != SubCommands.end() && + "Not a valid registered subcommand."); + OS << ActiveSubCommand->HelpText << "\n\n"; + if (!StringRef(ActiveSubCommand->Usage).empty()) + OS << "USAGE: " << ActiveSubCommand->Usage << "\n\n"; + } else { + OS << "USAGE: " << Usage << "\n\n"; + if (SubCommands.size() > 1) { + OS << "SUBCOMMANDS:\n\n"; + for (const auto &C : SubCommands) + OS << C.Name << " - " << C.HelpText << "\n"; + OS << "\n"; + } + } + + auto DoesOptionBelongToSubcommand = [&](const Info &CandidateInfo) { + // Retrieve the SubCommandIDs registered to the given current CandidateInfo + // Option. + ArrayRef SubCommandIDs = + CandidateInfo.getSubCommandIDs(SubCommandIDsTable); + + // If no registered subcommands, then only global options are to be printed. + // If no valid SubCommand (empty) in commandline then print the current + // global CandidateInfo Option. + if (SubCommandIDs.empty()) + return SubCommand.empty(); + + // Handle CandidateInfo Option which has at least one registered SubCommand. + // If no valid SubCommand (empty) in commandline, this CandidateInfo option + // should not be printed. + if (SubCommand.empty()) + return false; + + // Find the ID of the valid subcommand passed in commandline (its index in + // the SubCommands table which contains all subcommands). + unsigned ActiveSubCommandID = ActiveSubCommand - &SubCommands[0]; + // Print if the ActiveSubCommandID is registered with the CandidateInfo + // Option. + return std::find(SubCommandIDs.begin(), SubCommandIDs.end(), + ActiveSubCommandID) != SubCommandIDs.end(); + }; + for (unsigned Id = 1, e = getNumOptions() + 1; Id != e; ++Id) { // FIXME: Split out option groups. if (getOptionKind(Id) == Option::GroupClass) @@ -764,6 +814,9 @@ void OptTable::internalPrintHelp( if (ExcludeOption(CandidateInfo)) continue; + if (!DoesOptionBelongToSubcommand(CandidateInfo)) + continue; + // If an alias doesn't have a help text, show a help text for the aliased // option instead. const char *HelpText = getOptionHelpText(Id, VisibilityMask); @@ -791,8 +844,11 @@ void OptTable::internalPrintHelp( GenericOptTable::GenericOptTable(const StringTable &StrTable, ArrayRef PrefixesTable, - ArrayRef OptionInfos, bool IgnoreCase) - : OptTable(StrTable, PrefixesTable, OptionInfos, IgnoreCase) { + ArrayRef OptionInfos, bool IgnoreCase, + ArrayRef SubCommands, + ArrayRef SubCommandIDsTable) + : OptTable(StrTable, PrefixesTable, OptionInfos, IgnoreCase, SubCommands, + SubCommandIDsTable) { std::set TmpPrefixesUnion; for (auto const &Info : OptionInfos.drop_front(FirstSearchableIndex)) diff --git a/llvm/unittests/Option/CMakeLists.txt b/llvm/unittests/Option/CMakeLists.txt index 7be4300c0f3d2..5fefb5e85afde 100644 --- a/llvm/unittests/Option/CMakeLists.txt +++ b/llvm/unittests/Option/CMakeLists.txt @@ -4,11 +4,15 @@ set(LLVM_LINK_COMPONENTS ) set(LLVM_TARGET_DEFINITIONS Opts.td) - tablegen(LLVM Opts.inc -gen-opt-parser-defs) + +set(LLVM_TARGET_DEFINITIONS SubCommandOpts.td) +tablegen(LLVM SubCommandOpts.inc -gen-opt-parser-defs) + add_public_tablegen_target(OptsTestTableGen) add_llvm_unittest(OptionTests OptionParsingTest.cpp OptionMarshallingTest.cpp + OptionSubCommandsTest.cpp ) diff --git a/llvm/unittests/Option/OptionMarshallingTest.cpp b/llvm/unittests/Option/OptionMarshallingTest.cpp index 005144b91bf7f..15917cc05c51e 100644 --- a/llvm/unittests/Option/OptionMarshallingTest.cpp +++ b/llvm/unittests/Option/OptionMarshallingTest.cpp @@ -29,8 +29,9 @@ static const OptionWithMarshallingInfo MarshallingTable[] = { #define OPTION_WITH_MARSHALLING( \ PREFIX_TYPE, PREFIXED_NAME_OFFSET, ID, KIND, GROUP, ALIAS, ALIASARGS, \ FLAGS, VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, METAVAR, VALUES, \ - SHOULD_PARSE, ALWAYS_EMIT, KEYPATH, DEFAULT_VALUE, IMPLIED_CHECK, \ - IMPLIED_VALUE, NORMALIZER, DENORMALIZER, MERGER, EXTRACTOR, TABLE_INDEX) \ + SUBCOMMANDIDS_OFFSET, SHOULD_PARSE, ALWAYS_EMIT, KEYPATH, DEFAULT_VALUE, \ + IMPLIED_CHECK, IMPLIED_VALUE, NORMALIZER, DENORMALIZER, MERGER, EXTRACTOR, \ + TABLE_INDEX) \ {PREFIXED_NAME_OFFSET, #KEYPATH, #IMPLIED_CHECK, #IMPLIED_VALUE}, #include "Opts.inc" #undef OPTION_WITH_MARSHALLING diff --git a/llvm/unittests/Option/OptionSubCommandsTest.cpp b/llvm/unittests/Option/OptionSubCommandsTest.cpp new file mode 100644 index 0000000000000..e31a3262f135e --- /dev/null +++ b/llvm/unittests/Option/OptionSubCommandsTest.cpp @@ -0,0 +1,252 @@ +//===- unittest/Support/OptionParsingTest.cpp - OptTable tests ------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "llvm/ADT/STLExtras.h" +#include "llvm/Option/Arg.h" +#include "llvm/Option/ArgList.h" +#include "llvm/Option/OptTable.h" +#include "llvm/Option/Option.h" +#include "llvm/Support/raw_ostream.h" +#include "gtest/gtest.h" + +using namespace llvm; +using namespace llvm::opt; + +#if defined(__clang__) +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + +namespace { +enum ID { + OPT_INVALID = 0, +#define OPTION(PREFIXES, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, \ + VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, METAVAR, \ + VALUES, SUBCOMMANDIDS_OFFSET) \ + OPT_##ID, +#include "SubCommandOpts.inc" +#undef OPTION +}; +#define OPTTABLE_STR_TABLE_CODE +#include "SubCommandOpts.inc" +#undef OPTTABLE_STR_TABLE_CODE + +#define OPTTABLE_PREFIXES_TABLE_CODE +#include "SubCommandOpts.inc" +#undef OPTTABLE_PREFIXES_TABLE_CODE + +#define OPTTABLE_SUBCOMMAND_IDS_TABLE_CODE +#include "SubCommandOpts.inc" +#undef OPTTABLE_SUBCOMMAND_IDS_TABLE_CODE + +#define OPTTABLE_SUBCOMMANDS_CODE +#include "SubCommandOpts.inc" +#undef OPTTABLE_SUBCOMMANDS_CODE + +static constexpr OptTable::Info InfoTable[] = { +#define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__), +#include "SubCommandOpts.inc" +#undef OPTION +}; + +class TestOptSubCommandTable : public GenericOptTable { +public: + TestOptSubCommandTable(bool IgnoreCase = false) + : GenericOptTable(OptionStrTable, OptionPrefixesTable, InfoTable, + /*IgnoreCase=*/false, OptionSubCommands, + OptionSubCommandIDsTable) {} +}; + +// Test fixture +template class OptSubCommandTableTest : public ::testing::Test {}; + +// Test both precomputed and computed OptTables with the same suite of tests. +using OptSubCommandTableTestTypes = ::testing::Types; + +TYPED_TEST_SUITE(OptSubCommandTableTest, OptSubCommandTableTestTypes, ); + +TYPED_TEST(OptSubCommandTableTest, SubCommandParsing) { + TypeParam T; + unsigned MAI, MAC; + + std::string ErrMsg; + raw_string_ostream RSO1(ErrMsg); + + auto HandleMultipleSubcommands = [&](ArrayRef SubCommands) { + ErrMsg.clear(); + RSO1 << "Multiple subcommands passed\n"; + for (auto SC : SubCommands) + RSO1 << "\n" << SC; + }; + + auto HandleOtherPositionals = [&](ArrayRef Positionals) { + ErrMsg.clear(); + RSO1 << "Unregistered positionals passed\n"; + for (auto SC : Positionals) + RSO1 << "\n" << SC; + }; + + { + // Test case 1: Toplevel option, no subcommand + const char *Args[] = {"-version"}; + InputArgList AL = T.ParseArgs(Args, MAI, MAC); + EXPECT_TRUE(AL.hasArg(OPT_version)); + StringRef SC = AL.getSubCommand( + T.getSubCommands(), HandleMultipleSubcommands, HandleOtherPositionals); + EXPECT_TRUE(SC.empty()); + EXPECT_FALSE(AL.hasArg(OPT_uppercase)); + EXPECT_FALSE(AL.hasArg(OPT_lowercase)); + } + + { + // Test case 2: Subcommand 'foo' with its valid options + const char *Args[] = {"foo", "-uppercase"}; + InputArgList AL = T.ParseArgs(Args, MAI, MAC); + StringRef SC = AL.getSubCommand( + T.getSubCommands(), HandleMultipleSubcommands, HandleOtherPositionals); + EXPECT_EQ(SC, "foo"); + EXPECT_TRUE(AL.hasArg(OPT_uppercase)); + EXPECT_FALSE(AL.hasArg(OPT_lowercase)); + EXPECT_FALSE(AL.hasArg(OPT_version)); + EXPECT_EQ(std::string::npos, ErrMsg.find("Multiple subcommands passed")) + << "Did not expect error message as this is a valid use case."; + EXPECT_EQ(std::string::npos, ErrMsg.find("Unregistered positionals passed")) + << "Did not expect error message as this is a valid use case."; + } + + { + // Test case 3: Check valid use of subcommand which follows a valid + // subcommand option. + const char *Args[] = {"-uppercase", "foo"}; + InputArgList AL = T.ParseArgs(Args, MAI, MAC); + StringRef SC = AL.getSubCommand( + T.getSubCommands(), HandleMultipleSubcommands, HandleOtherPositionals); + EXPECT_EQ(SC, "foo"); + EXPECT_TRUE(AL.hasArg(OPT_uppercase)); + EXPECT_FALSE(AL.hasArg(OPT_lowercase)); + EXPECT_FALSE(AL.hasArg(OPT_version)); + EXPECT_EQ(std::string::npos, ErrMsg.find("Multiple subcommands passed")) + << "Did not expect error message as this is a valid use case."; + EXPECT_EQ(std::string::npos, ErrMsg.find("Unregistered positionals passed")) + << "Did not expect error message as this is a valid use case."; + } + + { + // Test case 4: Check invalid use of passing multiple subcommands. + const char *Args[] = {"-uppercase", "foo", "bar"}; + InputArgList AL = T.ParseArgs(Args, MAI, MAC); + StringRef SC = AL.getSubCommand( + T.getSubCommands(), HandleMultipleSubcommands, HandleOtherPositionals); + // No valid subcommand should be returned as this is an invalid invocation. + EXPECT_TRUE(SC.empty()); + // Expect the multiple subcommands error message. + EXPECT_NE(std::string::npos, ErrMsg.find("Multiple subcommands passed")); + EXPECT_NE(std::string::npos, ErrMsg.find("foo")); + EXPECT_NE(std::string::npos, ErrMsg.find("bar")); + EXPECT_EQ(std::string::npos, ErrMsg.find("Unregistered positionals passed")) + << "Did not expect error message as this is a valid use case."; + } + + { + // Test case 5: Check invalid use of passing unregistered subcommands. + const char *Args[] = {"foobar"}; + InputArgList AL = T.ParseArgs(Args, MAI, MAC); + StringRef SC = AL.getSubCommand( + T.getSubCommands(), HandleMultipleSubcommands, HandleOtherPositionals); + // No valid subcommand should be returned as this is an invalid invocation. + EXPECT_TRUE(SC.empty()); + // Expect the unregistered subcommands error message. + EXPECT_NE(std::string::npos, + ErrMsg.find("Unregistered positionals passed")); + EXPECT_NE(std::string::npos, ErrMsg.find("foobar")); + } + + { + // Test case 6: Check invalid use of a valid subcommand which follows a + // valid subcommand option but the option is not registered with the given + // subcommand. + const char *Args[] = {"-lowercase", "bar"}; + InputArgList AL = T.ParseArgs(Args, MAI, MAC); + StringRef SC = AL.getSubCommand( + T.getSubCommands(), HandleMultipleSubcommands, HandleOtherPositionals); + auto HandleSubCommandArg = [&](ID OptionType) { + if (!AL.hasArg(OptionType)) + return false; + auto O = T.getOption(OptionType); + if (!O.isRegisteredSC(SC)) { + ErrMsg.clear(); + RSO1 << "Option [" << O.getName() << "] is not valid for SubCommand [" + << SC << "]\n"; + return false; + } + return true; + }; + EXPECT_EQ(SC, "bar"); // valid subcommand + EXPECT_TRUE(AL.hasArg(OPT_lowercase)); // valid option + EXPECT_FALSE(HandleSubCommandArg(OPT_lowercase)); + EXPECT_NE( + std::string::npos, + ErrMsg.find("Option [lowercase] is not valid for SubCommand [bar]")); + } +} + +TYPED_TEST(OptSubCommandTableTest, SubCommandHelp) { + TypeParam T; + std::string Help; + raw_string_ostream RSO(Help); + + // Toplevel help + T.printHelp(RSO, "Test Usage String", "OverviewString"); + EXPECT_NE(std::string::npos, Help.find("OVERVIEW:")); + EXPECT_NE(std::string::npos, Help.find("OverviewString")); + EXPECT_NE(std::string::npos, Help.find("USAGE:")); + EXPECT_NE(std::string::npos, Help.find("Test Usage String")); + EXPECT_NE(std::string::npos, Help.find("SUBCOMMANDS:")); + EXPECT_NE(std::string::npos, Help.find("foo")); + EXPECT_NE(std::string::npos, Help.find("bar")); + EXPECT_NE(std::string::npos, Help.find("HelpText for SubCommand foo.")); + EXPECT_NE(std::string::npos, Help.find("HelpText for SubCommand bar.")); + EXPECT_NE(std::string::npos, Help.find("OPTIONS:")); + EXPECT_NE(std::string::npos, Help.find("--help")); + EXPECT_NE(std::string::npos, Help.find("-version")); + // uppercase is not a global option and should not be shown. + EXPECT_EQ(std::string::npos, Help.find("-uppercase")); + + // Help for subcommand foo + Help.clear(); + StringRef SC1 = "foo"; + T.printHelp(RSO, "Test Usage String", "OverviewString", false, false, + Visibility(), SC1); + EXPECT_NE(std::string::npos, Help.find("OVERVIEW:")); + EXPECT_NE(std::string::npos, Help.find("OverviewString")); + // SubCommand "foo" definition for tablegen has NO dedicated usage string so + // not expected to see USAGE. + EXPECT_EQ(std::string::npos, Help.find("USAGE:")); + EXPECT_NE(std::string::npos, Help.find("HelpText for SubCommand foo.")); + EXPECT_NE(std::string::npos, Help.find("-uppercase")); + EXPECT_NE(std::string::npos, Help.find("-lowercase")); + EXPECT_EQ(std::string::npos, Help.find("-version")); + EXPECT_EQ(std::string::npos, Help.find("SUBCOMMANDS:")); + + // Help for subcommand bar + Help.clear(); + StringRef SC2 = "bar"; + T.printHelp(RSO, "Test Usage String", "OverviewString", false, false, + Visibility(), SC2); + EXPECT_NE(std::string::npos, Help.find("OVERVIEW:")); + EXPECT_NE(std::string::npos, Help.find("OverviewString")); + // SubCommand "bar" definition for tablegen has a dedicated usage string. + EXPECT_NE(std::string::npos, Help.find("USAGE:")); + EXPECT_NE(std::string::npos, Help.find("Subcommand bar ")); + EXPECT_NE(std::string::npos, Help.find("HelpText for SubCommand bar.")); + EXPECT_NE(std::string::npos, Help.find("-uppercase")); + // lowercase is not an option for bar and should not be shown. + EXPECT_EQ(std::string::npos, Help.find("-lowercase")); + // version is a global option and should not be shown. + EXPECT_EQ(std::string::npos, Help.find("-version")); +} +} // end anonymous namespace diff --git a/llvm/unittests/Option/SubCommandOpts.td b/llvm/unittests/Option/SubCommandOpts.td new file mode 100644 index 0000000000000..b9750da0f3558 --- /dev/null +++ b/llvm/unittests/Option/SubCommandOpts.td @@ -0,0 +1,16 @@ +include "llvm/Option/OptParser.td" + +def sc_foo : SubCommand<"foo", "HelpText for SubCommand foo.">; + +def sc_bar : SubCommand<"bar", "HelpText for SubCommand bar.", + "Subcommand bar ">; + +def help : Flag<["--"], "help">, HelpText<"Subcommand ">; + +def version : Flag<["-"], "version">, HelpText<"Display the version number">; + +def uppercase : Flag<["-"], "uppercase", [sc_foo, sc_bar]>, + HelpText<"Print in uppercase">; + +def lowercase : Flag<["-"], "lowercase", [sc_foo]>, + HelpText<"Print in lowercase">; diff --git a/llvm/utils/TableGen/OptionParserEmitter.cpp b/llvm/utils/TableGen/OptionParserEmitter.cpp index a470fbbcadd58..48ae1a0a92b1c 100644 --- a/llvm/utils/TableGen/OptionParserEmitter.cpp +++ b/llvm/utils/TableGen/OptionParserEmitter.cpp @@ -9,8 +9,10 @@ #include "Common/OptEmitter.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallString.h" +#include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/Twine.h" +#include "llvm/Option/OptTable.h" #include "llvm/Support/InterleavedRange.h" #include "llvm/Support/raw_ostream.h" #include "llvm/TableGen/Record.h" @@ -258,6 +260,9 @@ static void emitOptionParser(const RecordKeeper &Records, raw_ostream &OS) { std::vector Opts = Records.getAllDerivedDefinitions("Option"); llvm::sort(Opts, IsOptionRecordsLess); + std::vector SubCommands = + Records.getAllDerivedDefinitions("SubCommand"); + emitSourceFileHeader("Option Parsing Definitions", OS); // Generate prefix groups. @@ -271,6 +276,35 @@ static void emitOptionParser(const RecordKeeper &Records, raw_ostream &OS) { Prefixes.try_emplace(PrefixKey, 0); } + // Generate sub command groups. + typedef SmallVector SubCommandKeyT; + typedef std::map SubCommandIDsT; + SubCommandIDsT SubCommandIDs; + + auto PrintSubCommandIdsOffset = [&SubCommandIDs, &OS](const Record &R) { + if (R.getValue("SubCommands") != nullptr) { + std::vector SubCommands = + R.getValueAsListOfDefs("SubCommands"); + SubCommandKeyT SubCommandKey; + for (const auto &SubCommand : SubCommands) + SubCommandKey.push_back(SubCommand->getName()); + OS << SubCommandIDs[SubCommandKey]; + } else { + // The option SubCommandIDsOffset (for default top level toolname is 0). + OS << " 0"; + } + }; + + SubCommandIDs.try_emplace(SubCommandKeyT(), 0); + for (const Record &R : llvm::make_pointee_range(Opts)) { + std::vector RSubCommands = + R.getValueAsListOfDefs("SubCommands"); + SubCommandKeyT SubCommandKey; + for (const auto &SubCommand : RSubCommands) + SubCommandKey.push_back(SubCommand->getName()); + SubCommandIDs.try_emplace(SubCommandKey, 0); + } + DenseSet PrefixesUnionSet; for (const auto &[Prefix, _] : Prefixes) PrefixesUnionSet.insert_range(Prefix); @@ -323,6 +357,40 @@ static void emitOptionParser(const RecordKeeper &Records, raw_ostream &OS) { OS << "\n};\n"; OS << "#endif // OPTTABLE_PREFIXES_TABLE_CODE\n\n"; + // Dump subcommand IDs. + OS << "/////////"; + OS << "// SubCommand IDs\n\n"; + OS << "#ifdef OPTTABLE_SUBCOMMAND_IDS_TABLE_CODE\n"; + OS << "static constexpr unsigned OptionSubCommandIDsTable[] = {\n"; + { + // Ensure the first subcommand set is always empty. + assert(!SubCommandIDs.empty() && + "We should always emit an empty set of subcommands"); + assert(SubCommandIDs.begin()->first.empty() && + "First subcommand set should always be empty"); + llvm::ListSeparator Sep(",\n"); + unsigned CurIndex = 0; + for (auto &[SubCommand, SubCommandIndex] : SubCommandIDs) { + // First emit the number of subcommand strings in this list of + // subcommands. + OS << Sep << " " << SubCommand.size() << " /* subcommands */"; + SubCommandIndex = CurIndex; + assert((CurIndex == 0 || !SubCommand.empty()) && + "Only first subcommand set should be empty!"); + for (const auto &SubCommandKey : SubCommand) { + auto It = std::find_if( + SubCommands.begin(), SubCommands.end(), + [&](const Record *R) { return R->getName() == SubCommandKey; }); + assert(It != SubCommands.end() && "SubCommand not found"); + OS << ", " << std::distance(SubCommands.begin(), It) << " /* '" + << SubCommandKey << "' */"; + } + CurIndex += SubCommand.size() + 1; + } + } + OS << "\n};\n"; + OS << "#endif // OPTTABLE_SUBCOMMAND_IDS_TABLE_CODE\n\n"; + // Dump prefixes union. OS << "/////////\n"; OS << "// Prefix Union\n\n"; @@ -400,7 +468,12 @@ static void emitOptionParser(const RecordKeeper &Records, raw_ostream &OS) { OS << ", nullptr"; // The option Values (unused for groups). - OS << ", nullptr)\n"; + OS << ", nullptr"; + + // The option SubCommandIDsOffset. + OS << ", "; + PrintSubCommandIdsOffset(R); + OS << ")\n"; } OS << "\n"; @@ -527,6 +600,10 @@ static void emitOptionParser(const RecordKeeper &Records, raw_ostream &OS) { OS << getOptionName(R) << "_Values"; else OS << "nullptr"; + + // The option SubCommandIDsOffset. + OS << ", "; + PrintSubCommandIdsOffset(R); }; auto IsMarshallingOption = [](const Record &R) { @@ -595,6 +672,19 @@ static void emitOptionParser(const RecordKeeper &Records, raw_ostream &OS) { OS << "#endif // SIMPLE_ENUM_VALUE_TABLE\n"; OS << "\n"; + OS << "/////////\n"; + OS << "\n// SubCommands\n\n"; + OS << "#ifdef OPTTABLE_SUBCOMMANDS_CODE\n"; + OS << "static constexpr llvm::opt::OptTable::SubCommand OptionSubCommands[] " + "= " + "{\n"; + for (const Record *SubCommand : SubCommands) { + OS << " { \"" << SubCommand->getValueAsString("Name") << "\", "; + OS << "\"" << SubCommand->getValueAsString("HelpText") << "\", "; + OS << "\"" << SubCommand->getValueAsString("Usage") << "\" },\n"; + } + OS << "};\n"; + OS << "#endif // OPTTABLE_SUBCOMMANDS_CODE\n\n"; OS << "\n"; }