[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:
Prabhu Rajasekaran 2025-10-06 12:11:00 -07:00 committed by GitHub
parent 8708968fba
commit fdbd17d1fb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 794 additions and 58 deletions

View File

@ -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

View File

@ -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 || \

View File

@ -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

View File

@ -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
};

View File

@ -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
};

View File

@ -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
};

View File

@ -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)

View 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}
)

View 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">;

View 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;
}

View File

@ -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
/// @{

View File

@ -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.

View File

@ -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

View File

@ -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).

View File

@ -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)

View File

@ -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))

View File

@ -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
)

View File

@ -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

View 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

View 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">;

View File

@ -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";
}