[llvm] Add subcommand support for OptTable (#155026)
Implement support for `subcommands` in OptTable to attain feature parity with `cl`. Design overview: https://discourse.llvm.org/t/subcommand-feature-support-in-llvm-opttable/88098 Issue: https://github.com/llvm/llvm-project/issues/108307
This commit is contained in:
parent
8708968fba
commit
fdbd17d1fb
@ -466,7 +466,7 @@ llvm::ArrayRef<ArgStripper::Rule> 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
|
||||
|
||||
@ -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 || \
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
};
|
||||
|
||||
@ -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
|
||||
};
|
||||
|
||||
@ -157,7 +157,7 @@ bool link(ArrayRef<const char *> 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
|
||||
};
|
||||
|
||||
@ -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)
|
||||
|
||||
19
llvm/examples/OptSubcommand/CMakeLists.txt
Normal file
19
llvm/examples/OptSubcommand/CMakeLists.txt
Normal file
@ -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}
|
||||
)
|
||||
18
llvm/examples/OptSubcommand/Opts.td
Normal file
18
llvm/examples/OptSubcommand/Opts.td
Normal file
@ -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 <options>">;
|
||||
|
||||
def help : Flag<["--"], "help">,
|
||||
HelpText<"OptSubcommand <subcommand> <options>">;
|
||||
|
||||
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">;
|
||||
137
llvm/examples/OptSubcommand/llvm-hello-sub.cpp
Normal file
137
llvm/examples/OptSubcommand/llvm-hello-sub.cpp
Normal file
@ -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<StringRef> 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<StringRef> 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;
|
||||
}
|
||||
@ -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 <algorithm>
|
||||
#include <cstddef>
|
||||
#include <initializer_list>
|
||||
@ -280,6 +281,22 @@ public:
|
||||
/// 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<OptTable::SubCommand> AllSubCommands,
|
||||
std::function<void(ArrayRef<StringRef>)> HandleMultipleSubcommands,
|
||||
std::function<void(ArrayRef<StringRef>)> HandleOtherPositionals) const;
|
||||
|
||||
/// @}
|
||||
/// @name Argument Lookup Utilities
|
||||
/// @{
|
||||
|
||||
@ -98,7 +98,15 @@ class HelpTextVariant<list<OptionVisibility> visibilities, string text> {
|
||||
string Text = text;
|
||||
}
|
||||
|
||||
class Option<list<string> prefixes, string name, OptionKind kind> {
|
||||
// Class definition for positional subcommands.
|
||||
class SubCommand<string name, string helpText, string usage = ""> {
|
||||
string Name = name;
|
||||
string HelpText = helpText;
|
||||
string Usage = usage;
|
||||
}
|
||||
|
||||
class Option<list<string> prefixes, string name, OptionKind kind,
|
||||
list<SubCommand> subcommands = []> {
|
||||
string EnumName = ?; // Uses the def name if undefined.
|
||||
list<string> Prefixes = prefixes;
|
||||
string Name = name;
|
||||
@ -129,26 +137,34 @@ class Option<list<string> prefixes, string name, OptionKind kind> {
|
||||
code ValueMerger = "mergeForwardValue";
|
||||
code ValueExtractor = "extractForwardValue";
|
||||
list<code> NormalizedValues = ?;
|
||||
list<SubCommand> SubCommands = subcommands;
|
||||
}
|
||||
|
||||
// Helpers for defining options.
|
||||
|
||||
class Flag<list<string> prefixes, string name>
|
||||
: Option<prefixes, name, KIND_FLAG>;
|
||||
class Joined<list<string> prefixes, string name>
|
||||
: Option<prefixes, name, KIND_JOINED>;
|
||||
class Separate<list<string> prefixes, string name>
|
||||
: Option<prefixes, name, KIND_SEPARATE>;
|
||||
class CommaJoined<list<string> prefixes, string name>
|
||||
: Option<prefixes, name, KIND_COMMAJOINED>;
|
||||
class MultiArg<list<string> prefixes, string name, int numargs>
|
||||
: Option<prefixes, name, KIND_MULTIARG> {
|
||||
class Flag<list<string> prefixes, string name,
|
||||
list<SubCommand> subcommands = []>
|
||||
: Option<prefixes, name, KIND_FLAG, subcommands>;
|
||||
class Joined<list<string> prefixes, string name,
|
||||
list<SubCommand> subcommands = []>
|
||||
: Option<prefixes, name, KIND_JOINED, subcommands>;
|
||||
class Separate<list<string> prefixes, string name,
|
||||
list<SubCommand> subcommands = []>
|
||||
: Option<prefixes, name, KIND_SEPARATE, subcommands>;
|
||||
class CommaJoined<list<string> prefixes, string name,
|
||||
list<SubCommand> subcommands = []>
|
||||
: Option<prefixes, name, KIND_COMMAJOINED, subcommands>;
|
||||
class MultiArg<list<string> prefixes, string name, int numargs,
|
||||
list<SubCommand> subcommands = []>
|
||||
: Option<prefixes, name, KIND_MULTIARG, subcommands> {
|
||||
int NumArgs = numargs;
|
||||
}
|
||||
class JoinedOrSeparate<list<string> prefixes, string name>
|
||||
: Option<prefixes, name, KIND_JOINED_OR_SEPARATE>;
|
||||
class JoinedAndSeparate<list<string> prefixes, string name>
|
||||
: Option<prefixes, name, KIND_JOINED_AND_SEPARATE>;
|
||||
class JoinedOrSeparate<list<string> prefixes, string name,
|
||||
list<SubCommand> subcommands = []>
|
||||
: Option<prefixes, name, KIND_JOINED_OR_SEPARATE, subcommands>;
|
||||
class JoinedAndSeparate<list<string> prefixes, string name,
|
||||
list<SubCommand> subcommands = []>
|
||||
: Option<prefixes, name, KIND_JOINED_AND_SEPARATE, subcommands>;
|
||||
|
||||
// Mix-ins for adding optional attributes.
|
||||
|
||||
|
||||
@ -53,6 +53,13 @@ public:
|
||||
/// 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 @@ public:
|
||||
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 @@ public:
|
||||
getNumPrefixes(PrefixesTable));
|
||||
}
|
||||
|
||||
bool hasSubCommands() const { return SubCommandIDsOffset != 0; }
|
||||
|
||||
unsigned getNumSubCommandIDs(ArrayRef<unsigned> SubCommandIDsTable) const {
|
||||
// We embed the number of subcommand IDs in the value of the first offset.
|
||||
return SubCommandIDsTable[SubCommandIDsOffset];
|
||||
}
|
||||
|
||||
ArrayRef<unsigned>
|
||||
getSubCommandIDs(ArrayRef<unsigned> SubCommandIDsTable) const {
|
||||
return hasSubCommands() ? SubCommandIDsTable.slice(
|
||||
SubCommandIDsOffset + 1,
|
||||
getNumSubCommandIDs(SubCommandIDsTable))
|
||||
: ArrayRef<unsigned>();
|
||||
}
|
||||
|
||||
void appendPrefixes(const StringTable &StrTable,
|
||||
ArrayRef<StringTable::Offset> PrefixesTable,
|
||||
SmallVectorImpl<StringRef> &Prefixes) const {
|
||||
@ -119,6 +143,22 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
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 @@ private:
|
||||
ArrayRef<Info> OptionInfos;
|
||||
|
||||
bool IgnoreCase;
|
||||
|
||||
/// The subcommand information table.
|
||||
ArrayRef<SubCommand> SubCommands;
|
||||
|
||||
/// The subcommand IDs table.
|
||||
ArrayRef<unsigned> SubCommandIDsTable;
|
||||
|
||||
bool GroupedShortOptions = false;
|
||||
bool DashDashParsing = false;
|
||||
const char *EnvVar = nullptr;
|
||||
@ -168,7 +215,9 @@ protected:
|
||||
/// manually call \c buildPrefixChars once they are fully constructed.
|
||||
OptTable(const StringTable &StrTable,
|
||||
ArrayRef<StringTable::Offset> PrefixesTable,
|
||||
ArrayRef<Info> OptionInfos, bool IgnoreCase = false);
|
||||
ArrayRef<Info> OptionInfos, bool IgnoreCase = false,
|
||||
ArrayRef<SubCommand> SubCommands = {},
|
||||
ArrayRef<unsigned> SubCommandIDsTable = {});
|
||||
|
||||
/// Build (or rebuild) the PrefixChars member.
|
||||
void buildPrefixChars();
|
||||
@ -179,6 +228,8 @@ public:
|
||||
/// Return the string table used for option names.
|
||||
const StringTable &getStrTable() const { return *StrTable; }
|
||||
|
||||
ArrayRef<SubCommand> getSubCommands() const { return SubCommands; }
|
||||
|
||||
/// Return the prefixes table used for option names.
|
||||
ArrayRef<StringTable::Offset> getPrefixesTable() const {
|
||||
return PrefixesTable;
|
||||
@ -410,7 +461,8 @@ public:
|
||||
/// 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 @@ public:
|
||||
|
||||
private:
|
||||
void internalPrintHelp(raw_ostream &OS, const char *Usage, const char *Title,
|
||||
bool ShowHidden, bool ShowAllAliases,
|
||||
StringRef SubCommand, bool ShowHidden,
|
||||
bool ShowAllAliases,
|
||||
std::function<bool(const Info &)> ExcludeOption,
|
||||
Visibility VisibilityMask) const;
|
||||
};
|
||||
@ -428,7 +481,9 @@ class GenericOptTable : public OptTable {
|
||||
protected:
|
||||
LLVM_ABI GenericOptTable(const StringTable &StrTable,
|
||||
ArrayRef<StringTable::Offset> PrefixesTable,
|
||||
ArrayRef<Info> OptionInfos, bool IgnoreCase = false);
|
||||
ArrayRef<Info> OptionInfos, bool IgnoreCase = false,
|
||||
ArrayRef<SubCommand> SubCommands = {},
|
||||
ArrayRef<unsigned> SubCommandIDsTable = {});
|
||||
};
|
||||
|
||||
class PrecomputedOptTable : public OptTable {
|
||||
@ -437,8 +492,11 @@ protected:
|
||||
ArrayRef<StringTable::Offset> PrefixesTable,
|
||||
ArrayRef<Info> OptionInfos,
|
||||
ArrayRef<StringTable::Offset> PrefixesUnionOffsets,
|
||||
bool IgnoreCase = false)
|
||||
: OptTable(StrTable, PrefixesTable, OptionInfos, IgnoreCase) {
|
||||
bool IgnoreCase = false,
|
||||
ArrayRef<SubCommand> SubCommands = {},
|
||||
ArrayRef<unsigned> SubCommandIDsTable = {})
|
||||
: OptTable(StrTable, PrefixesTable, OptionInfos, IgnoreCase, SubCommands,
|
||||
SubCommandIDsTable) {
|
||||
for (auto PrefixOffset : PrefixesUnionOffsets)
|
||||
PrefixesUnion.push_back(StrTable[PrefixOffset]);
|
||||
buildPrefixChars();
|
||||
@ -452,33 +510,36 @@ protected:
|
||||
#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
|
||||
|
||||
@ -216,6 +216,12 @@ public:
|
||||
/// 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).
|
||||
|
||||
@ -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 <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@ -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<OptTable::SubCommand> AllSubCommands,
|
||||
std::function<void(ArrayRef<StringRef>)> HandleMultipleSubcommands,
|
||||
std::function<void(ArrayRef<StringRef>)> HandleOtherPositionals) const {
|
||||
|
||||
SmallVector<StringRef, 4> SubCommands;
|
||||
SmallVector<StringRef, 4> 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)
|
||||
|
||||
@ -79,9 +79,12 @@ OptSpecifier::OptSpecifier(const Option *Opt) : ID(Opt->getID()) {}
|
||||
|
||||
OptTable::OptTable(const StringTable &StrTable,
|
||||
ArrayRef<StringTable::Offset> PrefixesTable,
|
||||
ArrayRef<Info> OptionInfos, bool IgnoreCase)
|
||||
ArrayRef<Info> OptionInfos, bool IgnoreCase,
|
||||
ArrayRef<SubCommand> SubCommands,
|
||||
ArrayRef<unsigned> 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<bool(const Info &)> ExcludeOption,
|
||||
raw_ostream &OS, const char *Usage, const char *Title, StringRef SubCommand,
|
||||
bool ShowHidden, bool ShowAllAliases,
|
||||
std::function<bool(const Info &)> 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<std::string, std::vector<OptionInfo>> 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<unsigned> 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<StringTable::Offset> PrefixesTable,
|
||||
ArrayRef<Info> OptionInfos, bool IgnoreCase)
|
||||
: OptTable(StrTable, PrefixesTable, OptionInfos, IgnoreCase) {
|
||||
ArrayRef<Info> OptionInfos, bool IgnoreCase,
|
||||
ArrayRef<SubCommand> SubCommands,
|
||||
ArrayRef<unsigned> SubCommandIDsTable)
|
||||
: OptTable(StrTable, PrefixesTable, OptionInfos, IgnoreCase, SubCommands,
|
||||
SubCommandIDsTable) {
|
||||
|
||||
std::set<StringRef> TmpPrefixesUnion;
|
||||
for (auto const &Info : OptionInfos.drop_front(FirstSearchableIndex))
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
@ -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
|
||||
|
||||
252
llvm/unittests/Option/OptionSubCommandsTest.cpp
Normal file
252
llvm/unittests/Option/OptionSubCommandsTest.cpp
Normal file
@ -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 <typename T> class OptSubCommandTableTest : public ::testing::Test {};
|
||||
|
||||
// Test both precomputed and computed OptTables with the same suite of tests.
|
||||
using OptSubCommandTableTestTypes = ::testing::Types<TestOptSubCommandTable>;
|
||||
|
||||
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<StringRef> SubCommands) {
|
||||
ErrMsg.clear();
|
||||
RSO1 << "Multiple subcommands passed\n";
|
||||
for (auto SC : SubCommands)
|
||||
RSO1 << "\n" << SC;
|
||||
};
|
||||
|
||||
auto HandleOtherPositionals = [&](ArrayRef<StringRef> 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 <options>"));
|
||||
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
|
||||
16
llvm/unittests/Option/SubCommandOpts.td
Normal file
16
llvm/unittests/Option/SubCommandOpts.td
Normal file
@ -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 <options>">;
|
||||
|
||||
def help : Flag<["--"], "help">, HelpText<"Subcommand <subcommand> <options>">;
|
||||
|
||||
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">;
|
||||
@ -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<const Record *> Opts = Records.getAllDerivedDefinitions("Option");
|
||||
llvm::sort(Opts, IsOptionRecordsLess);
|
||||
|
||||
std::vector<const Record *> 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<StringRef, 2> SubCommandKeyT;
|
||||
typedef std::map<SubCommandKeyT, unsigned> SubCommandIDsT;
|
||||
SubCommandIDsT SubCommandIDs;
|
||||
|
||||
auto PrintSubCommandIdsOffset = [&SubCommandIDs, &OS](const Record &R) {
|
||||
if (R.getValue("SubCommands") != nullptr) {
|
||||
std::vector<const Record *> 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<const Record *> RSubCommands =
|
||||
R.getValueAsListOfDefs("SubCommands");
|
||||
SubCommandKeyT SubCommandKey;
|
||||
for (const auto &SubCommand : RSubCommands)
|
||||
SubCommandKey.push_back(SubCommand->getName());
|
||||
SubCommandIDs.try_emplace(SubCommandKey, 0);
|
||||
}
|
||||
|
||||
DenseSet<StringRef> 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";
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user