[clang] [clang-tools-extra] [lld] [llvm] [llvm] Add subcommand support for OptTable (PR #155026)
Prabhu Rajasekaran via llvm-commits
llvm-commits at lists.llvm.org
Tue Aug 26 17:53:39 PDT 2025
https://github.com/Prabhuk updated https://github.com/llvm/llvm-project/pull/155026
>From 3584c6aacac629c51c4d1e8e08258c0c24f3a165 Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Tue, 19 Aug 2025 15:48:47 -0700
Subject: [PATCH 01/10] [OptTable] Subcommand support.
TODO: Add tests.
---
clang-tools-extra/clangd/CompileCommands.cpp | 2 +-
clang/lib/Frontend/CompilerInvocation.cpp | 11 +-
clang/tools/clang-installapi/Options.h | 2 +-
lld/MachO/DriverUtils.cpp | 5 +-
lld/MinGW/Driver.cpp | 5 +-
lld/wasm/Driver.cpp | 5 +-
llvm/examples/CMakeLists.txt | 1 +
llvm/examples/OptSubcommand/CMakeLists.txt | 19 +++
llvm/examples/OptSubcommand/Opts.td | 16 +++
.../examples/OptSubcommand/llvm-hello-sub.cpp | 94 ++++++++++++++
llvm/include/llvm/Option/ArgList.h | 3 +
llvm/include/llvm/Option/OptParser.td | 58 ++++++---
llvm/include/llvm/Option/OptTable.h | 85 ++++++++++---
llvm/lib/Option/ArgList.cpp | 12 ++
llvm/lib/Option/OptTable.cpp | 116 +++++++++++++++---
.../Option/OptionMarshallingTest.cpp | 5 +-
llvm/utils/TableGen/OptionParserEmitter.cpp | 99 ++++++++++++++-
17 files changed, 473 insertions(+), 65 deletions(-)
create mode 100644 llvm/examples/OptSubcommand/CMakeLists.txt
create mode 100644 llvm/examples/OptSubcommand/Opts.td
create mode 100644 llvm/examples/OptSubcommand/llvm-hello-sub.cpp
diff --git a/clang-tools-extra/clangd/CompileCommands.cpp b/clang-tools-extra/clangd/CompileCommands.cpp
index 80391fe8cce25..7ab04894a63b5 100644
--- a/clang-tools-extra/clangd/CompileCommands.cpp
+++ b/clang-tools-extra/clangd/CompileCommands.cpp
@@ -465,7 +465,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, COMMANDIDS_OFFSET) \
{DriverID::OPT_##ID, DriverID::OPT_##ALIAS, ALIASARGS},
#include "clang/Driver/Options.inc"
#undef OPTION
diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp
index a4d18966be35f..681feda59c623 100644
--- a/clang/lib/Frontend/CompilerInvocation.cpp
+++ b/clang/lib/Frontend/CompilerInvocation.cpp
@@ -533,9 +533,9 @@ static T extractMaskValue(T KeyPath) {
#define PARSE_OPTION_WITH_MARSHALLING( \
ARGS, DIAGS, PREFIX_TYPE, SPELLING_OFFSET, ID, KIND, GROUP, ALIAS, \
ALIASARGS, FLAGS, VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, \
- METAVAR, VALUES, SHOULD_PARSE, ALWAYS_EMIT, KEYPATH, DEFAULT_VALUE, \
- IMPLIED_CHECK, IMPLIED_VALUE, NORMALIZER, DENORMALIZER, MERGER, EXTRACTOR, \
- TABLE_INDEX) \
+ METAVAR, VALUES, COMMANDIDS_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) \
+ COMMANDIDS_OFFSET, SHOULD_PARSE, ALWAYS_EMIT, KEYPATH, DEFAULT_VALUE, \
+ IMPLIED_CHECK, IMPLIED_VALUE, NORMALIZER, DENORMALIZER, MERGER, EXTRACTOR, \
+ TABLE_INDEX) \
if ((VISIBILITY) & options::CC1Option) { \
[&](const auto &Extracted) { \
if (ALWAYS_EMIT || \
diff --git a/clang/tools/clang-installapi/Options.h b/clang/tools/clang-installapi/Options.h
index d62f2efd3141a..918723a334a09 100644
--- a/clang/tools/clang-installapi/Options.h
+++ b/clang/tools/clang-installapi/Options.h
@@ -208,7 +208,7 @@ enum ID {
OPT_INVALID = 0, // This is not an option ID.
#define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, \
VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, METAVAR, \
- VALUES) \
+ VALUES, COMMANDIDS_OFFSET) \
OPT_##ID,
#include "InstallAPIOpts.inc"
LastOption
diff --git a/lld/MachO/DriverUtils.cpp b/lld/MachO/DriverUtils.cpp
index a3b722f13daca..da7653d15b7f2 100644
--- a/lld/MachO/DriverUtils.cpp
+++ b/lld/MachO/DriverUtils.cpp
@@ -45,7 +45,7 @@ using namespace lld::macho;
static constexpr OptTable::Info optInfo[] = {
#define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, \
VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, METAVAR, \
- VALUES) \
+ VALUES, COMMANDIDS_OFFSET) \
{PREFIX, \
NAME, \
HELPTEXT, \
@@ -59,7 +59,8 @@ static constexpr OptTable::Info optInfo[] = {
OPT_##GROUP, \
OPT_##ALIAS, \
ALIASARGS, \
- VALUES},
+ VALUES, \
+ COMMANDIDS_OFFSET},
#include "Options.inc"
#undef OPTION
};
diff --git a/lld/MinGW/Driver.cpp b/lld/MinGW/Driver.cpp
index 5098dbd77b4fd..306c55135b677 100644
--- a/lld/MinGW/Driver.cpp
+++ b/lld/MinGW/Driver.cpp
@@ -69,7 +69,7 @@ enum {
static constexpr opt::OptTable::Info infoTable[] = {
#define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, \
VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, METAVAR, \
- VALUES) \
+ VALUES, COMMANDIDS_OFFSET) \
{PREFIX, \
NAME, \
HELPTEXT, \
@@ -83,7 +83,8 @@ static constexpr opt::OptTable::Info infoTable[] = {
OPT_##GROUP, \
OPT_##ALIAS, \
ALIASARGS, \
- VALUES},
+ VALUES, \
+ COMMANDIDS_OFFSET},
#include "Options.inc"
#undef OPTION
};
diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp
index 1c5d21c06f5af..baf61da44fad9 100644
--- a/lld/wasm/Driver.cpp
+++ b/lld/wasm/Driver.cpp
@@ -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, COMMANDIDS_OFFSET) \
{PREFIX, \
NAME, \
HELPTEXT, \
@@ -171,7 +171,8 @@ static constexpr opt::OptTable::Info optInfo[] = {
OPT_##GROUP, \
OPT_##ALIAS, \
ALIASARGS, \
- VALUES},
+ VALUES, \
+ COMMANDIDS_OFFSET},
#include "Options.inc"
#undef OPTION
};
diff --git a/llvm/examples/CMakeLists.txt b/llvm/examples/CMakeLists.txt
index 74613bd1350bd..b10a94c5493b8 100644
--- a/llvm/examples/CMakeLists.txt
+++ b/llvm/examples/CMakeLists.txt
@@ -8,6 +8,7 @@ add_subdirectory(ModuleMaker)
add_subdirectory(OrcV2Examples)
add_subdirectory(SpeculativeJIT)
add_subdirectory(Bye)
+add_subdirectory(OptSubcommand)
if(LLVM_ENABLE_EH AND (NOT WIN32) AND (NOT "${LLVM_NATIVE_ARCH}" STREQUAL "ARM"))
add_subdirectory(ExceptionDemo)
diff --git a/llvm/examples/OptSubcommand/CMakeLists.txt b/llvm/examples/OptSubcommand/CMakeLists.txt
new file mode 100644
index 0000000000000..debc948611866
--- /dev/null
+++ b/llvm/examples/OptSubcommand/CMakeLists.txt
@@ -0,0 +1,19 @@
+# Set the .td file to be processed for this target.
+set(LLVM_TARGET_DEFINITIONS Opts.td)
+
+tablegen(LLVM Opts.inc -gen-opt-parser-defs)
+add_public_tablegen_target(HelloSubTableGen)
+
+set(LLVM_LINK_COMPONENTS
+ Support
+ Option
+ )
+
+add_llvm_example(OptSubcommand
+ llvm-hello-sub.cpp
+ )
+
+target_include_directories(OptSubcommand
+ PRIVATE
+ ${CMAKE_CURRENT_BINARY_DIR}
+ )
diff --git a/llvm/examples/OptSubcommand/Opts.td b/llvm/examples/OptSubcommand/Opts.td
new file mode 100644
index 0000000000000..8c4f66b8c3043
--- /dev/null
+++ b/llvm/examples/OptSubcommand/Opts.td
@@ -0,0 +1,16 @@
+include "llvm/Option/OptParser.td"
+
+def sc_foo : Subcommand<"foo", "HelpText for Subcommand foo.">;
+
+def sc_bar : Subcommand<"bar", "HelpText for Subcommand bar.">;
+
+def help : Flag<["--"], "help">, HelpText<"Top Level Help Text for the tool.">;
+
+def version : Flag<["-"], "version">,
+ HelpText<"Toplevel Display the version number">;
+
+def uppercase : Flag<["-"], "uppercase", [sc_foo, sc_bar]>,
+ HelpText<"Print in uppercase">;
+
+def lowercase : Flag<["-"], "lowercase", [sc_foo]>,
+ HelpText<"Print in lowercase">;
diff --git a/llvm/examples/OptSubcommand/llvm-hello-sub.cpp b/llvm/examples/OptSubcommand/llvm-hello-sub.cpp
new file mode 100644
index 0000000000000..aecc981e485c1
--- /dev/null
+++ b/llvm/examples/OptSubcommand/llvm-hello-sub.cpp
@@ -0,0 +1,94 @@
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/Option/ArgList.h"
+#include "llvm/Option/OptTable.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, COMMANDIDS_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_COMMAND_IDS_TABLE_CODE
+#include "Opts.inc"
+#undef OPTTABLE_COMMAND_IDS_TABLE_CODE
+
+#define OPTTABLE_COMMANDS_CODE
+#include "Opts.inc"
+#undef OPTTABLE_COMMANDS_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,
+ OptionCommands, OptionCommandIDsTable) {}
+};
+} // namespace
+
+int main(int argc, char **argv) {
+ InitLLVM X(argc, argv);
+ HelloSubOptTable T;
+ unsigned MissingArgIndex, MissingArgCount;
+ InputArgList Args = T.ParseArgs(ArrayRef(argv + 1, argc - 1), MissingArgIndex,
+ MissingArgCount);
+
+ StringRef Subcommand = Args.getSubcommand();
+ if (Args.hasArg(OPT_help)) {
+ T.printHelp(llvm::outs(), "llvm-hello-sub [subcommand] [options]",
+ "LLVM Hello Subcommand Example", false, false, Visibility(),
+ Subcommand);
+ return 0;
+ }
+
+ if (Args.hasArg(OPT_version)) {
+ llvm::outs() << "LLVM Hello Subcommand Example 1.0\n";
+ return 0;
+ }
+
+ if (Subcommand == "foo") {
+ if (Args.hasArg(OPT_uppercase))
+ llvm::outs() << "FOO\n";
+ else if (Args.hasArg(OPT_lowercase))
+ llvm::outs() << "foo\n";
+ else
+ llvm::errs() << "error: unknown option for subcommand '" << Subcommand
+ << "'. See -help.\n";
+ return 1;
+ } else if (Subcommand == "bar") {
+ if (Args.hasArg(OPT_lowercase))
+ llvm::outs() << "bar\n";
+ else if (Args.hasArg(OPT_uppercase))
+ llvm::outs() << "BAR\n";
+ else
+ llvm::errs() << "error: unknown option for subcommand '" << Subcommand
+ << "'. See -help.\n";
+ } else {
+ llvm::errs() << "error: unknown subcommand '" << Subcommand
+ << "'. See --help.\n";
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/llvm/include/llvm/Option/ArgList.h b/llvm/include/llvm/Option/ArgList.h
index 313164bc29689..2394b2e8301b8 100644
--- a/llvm/include/llvm/Option/ArgList.h
+++ b/llvm/include/llvm/Option/ArgList.h
@@ -280,6 +280,9 @@ class ArgList {
/// list.
virtual unsigned getNumInputArgStrings() const = 0;
+ /// getSubcommand - Return the active subcommand, if one exists.
+ LLVM_ABI StringRef getSubcommand() const;
+
/// @}
/// @name Argument Lookup Utilities
/// @{
diff --git a/llvm/include/llvm/Option/OptParser.td b/llvm/include/llvm/Option/OptParser.td
index 9fd606b0d6fcb..2bf920be8d946 100644
--- a/llvm/include/llvm/Option/OptParser.td
+++ b/llvm/include/llvm/Option/OptParser.td
@@ -98,7 +98,21 @@ class HelpTextVariant<list<OptionVisibility> visibilities, string text> {
string Text = text;
}
-class Option<list<string> prefixes, string name, OptionKind kind> {
+// Base class for TopLevelCommand and Subcommands.
+class Command<string name> { string Name = name; }
+
+// Class definition for positional subcommands.
+class Subcommand<string name, string helpText> : Command<name> {
+ string HelpText = helpText;
+}
+
+// Compile time representation for top level command (aka toolname).
+// Offers backward compatibility with existing Option class definitions before
+// introduction of commandGroup in Option class to support subcommands.
+def TopLevelCommand : Command<"TopLevelCommand">;
+
+class Option<list<string> prefixes, string name, OptionKind kind,
+ list<Command> commandGroup = [TopLevelCommand]> {
string EnumName = ?; // Uses the def name if undefined.
list<string> Prefixes = prefixes;
string Name = name;
@@ -129,26 +143,34 @@ class Option<list<string> prefixes, string name, OptionKind kind> {
code ValueMerger = "mergeForwardValue";
code ValueExtractor = "extractForwardValue";
list<code> NormalizedValues = ?;
+ list<Command> CommandGroup = commandGroup;
}
// 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<Command> commandGroup = [TopLevelCommand]>
+ : Option<prefixes, name, KIND_FLAG, commandGroup>;
+class Joined<list<string> prefixes, string name,
+ list<Command> commandGroup = [TopLevelCommand]>
+ : Option<prefixes, name, KIND_JOINED, commandGroup>;
+class Separate<list<string> prefixes, string name,
+ list<Command> commandGroup = [TopLevelCommand]>
+ : Option<prefixes, name, KIND_SEPARATE, commandGroup>;
+class CommaJoined<list<string> prefixes, string name,
+ list<Command> commandGroup = [TopLevelCommand]>
+ : Option<prefixes, name, KIND_COMMAJOINED, commandGroup>;
+class MultiArg<list<string> prefixes, string name, int numargs,
+ list<Command> commandGroup = [TopLevelCommand]>
+ : Option<prefixes, name, KIND_MULTIARG, commandGroup> {
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<Command> commandGroup = [TopLevelCommand]>
+ : Option<prefixes, name, KIND_JOINED_OR_SEPARATE, commandGroup>;
+class JoinedAndSeparate<list<string> prefixes, string name,
+ list<Command> commandGroup = [TopLevelCommand]>
+ : Option<prefixes, name, KIND_JOINED_AND_SEPARATE, commandGroup>;
// Mix-ins for adding optional attributes.
@@ -271,7 +293,7 @@ class ValueExtractor<code extractor> { code ValueExtractor = extractor; }
// FIXME: Have generator validate that these appear in correct position (and
// aren't duplicated).
-def INPUT : Option<[], "<input>", KIND_INPUT>;
-def UNKNOWN : Option<[], "<unknown>", KIND_UNKNOWN>;
+def INPUT : Option<[], "<input>", KIND_INPUT, [TopLevelCommand]>;
+def UNKNOWN : Option<[], "<unknown>", KIND_UNKNOWN, [TopLevelCommand]>;
-#endif // LLVM_OPTION_OPTPARSER_TD
+#endif // LLVM_OPTION_OPTPARSER_TD
\ No newline at end of file
diff --git a/llvm/include/llvm/Option/OptTable.h b/llvm/include/llvm/Option/OptTable.h
index df42ee341ee58..3f362521a7e64 100644
--- a/llvm/include/llvm/Option/OptTable.h
+++ b/llvm/include/llvm/Option/OptTable.h
@@ -10,6 +10,7 @@
#define LLVM_OPTION_OPTTABLE_H
#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/StringTable.h"
@@ -53,6 +54,12 @@ class Visibility {
/// parts of the driver still use Option instances where convenient.
class LLVM_ABI OptTable {
public:
+ /// Represents a subcommand and its options in the option table.
+ struct Command {
+ const char *Name;
+ const char *HelpText;
+ };
+
/// Entry for a single option instance in the option data table.
struct Info {
unsigned PrefixesOffset;
@@ -79,6 +86,7 @@ class LLVM_ABI OptTable {
unsigned short AliasID;
const char *AliasArgs;
const char *Values;
+ unsigned CommandIDsOffset;
bool hasNoPrefix() const { return PrefixesOffset == 0; }
@@ -94,6 +102,20 @@ class LLVM_ABI OptTable {
getNumPrefixes(PrefixesTable));
}
+ bool hasCommands() const { return CommandIDsOffset != 0; }
+
+ unsigned getNumCommandIDs(ArrayRef<unsigned> CommandIDsTable) const {
+ // We embed the number of command IDs in the value of the first offset.
+ return CommandIDsTable[CommandIDsOffset];
+ }
+
+ ArrayRef<unsigned> getCommandIDs(ArrayRef<unsigned> CommandIDsTable) const {
+ return hasCommands()
+ ? CommandIDsTable.slice(CommandIDsOffset + 1,
+ getNumCommandIDs(CommandIDsTable))
+ : ArrayRef<unsigned>();
+ }
+
void appendPrefixes(const StringTable &StrTable,
ArrayRef<StringTable::Offset> PrefixesTable,
SmallVectorImpl<StringRef> &Prefixes) const {
@@ -133,6 +155,12 @@ class LLVM_ABI OptTable {
/// The option information table.
ArrayRef<Info> OptionInfos;
+ /// The command information table.
+ ArrayRef<Command> Commands;
+
+ /// The command IDs table.
+ ArrayRef<unsigned> CommandIDsTable;
+
bool IgnoreCase;
bool GroupedShortOptions = false;
bool DashDashParsing = false;
@@ -169,6 +197,10 @@ class LLVM_ABI OptTable {
OptTable(const StringTable &StrTable,
ArrayRef<StringTable::Offset> PrefixesTable,
ArrayRef<Info> OptionInfos, bool IgnoreCase = false);
+ OptTable(const StringTable &StrTable,
+ ArrayRef<StringTable::Offset> PrefixesTable,
+ ArrayRef<Info> OptionInfos, ArrayRef<Command> Commands,
+ ArrayRef<unsigned> CommandIDsTable, bool IgnoreCase = false);
/// Build (or rebuild) the PrefixChars member.
void buildPrefixChars();
@@ -350,6 +382,7 @@ class LLVM_ABI OptTable {
private:
std::unique_ptr<Arg>
internalParseOneArg(const ArgList &Args, unsigned &Index,
+ const Command *ActiveCommand,
std::function<bool(const Option &)> ExcludeOption) const;
public:
@@ -410,7 +443,8 @@ class LLVM_ABI OptTable {
/// texts.
void printHelp(raw_ostream &OS, const char *Usage, const char *Title,
bool ShowHidden = false, bool ShowAllAliases = false,
- Visibility VisibilityMask = Visibility()) const;
+ Visibility VisibilityMask = Visibility(),
+ StringRef SubCommand = {}) const;
void printHelp(raw_ostream &OS, const char *Usage, const char *Title,
unsigned FlagsToInclude, unsigned FlagsToExclude,
@@ -418,7 +452,8 @@ class LLVM_ABI OptTable {
private:
void internalPrintHelp(raw_ostream &OS, const char *Usage, const char *Title,
- bool ShowHidden, bool ShowAllAliases,
+ StringRef Subcommand, bool ShowHidden,
+ bool ShowAllAliases,
std::function<bool(const Info &)> ExcludeOption,
Visibility VisibilityMask) const;
};
@@ -428,21 +463,38 @@ 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)
+ : GenericOptTable(StrTable, PrefixesTable, OptionInfos, {}, {},
+ IgnoreCase) {}
+ LLVM_ABI GenericOptTable(const StringTable &StrTable,
+ ArrayRef<StringTable::Offset> PrefixesTable,
+ ArrayRef<Info> OptionInfos,
+ ArrayRef<Command> Commands,
+ ArrayRef<unsigned> CommandIDsTable,
+ bool IgnoreCase = false);
};
class PrecomputedOptTable : public OptTable {
protected:
PrecomputedOptTable(const StringTable &StrTable,
ArrayRef<StringTable::Offset> PrefixesTable,
- ArrayRef<Info> OptionInfos,
+ ArrayRef<Info> OptionInfos, ArrayRef<Command> Commands,
+ ArrayRef<unsigned> CommandIDsTable,
ArrayRef<StringTable::Offset> PrefixesUnionOffsets,
bool IgnoreCase = false)
- : OptTable(StrTable, PrefixesTable, OptionInfos, IgnoreCase) {
+ : OptTable(StrTable, PrefixesTable, OptionInfos, Commands,
+ CommandIDsTable, IgnoreCase) {
for (auto PrefixOffset : PrefixesUnionOffsets)
PrefixesUnion.push_back(StrTable[PrefixOffset]);
buildPrefixChars();
}
+ PrecomputedOptTable(const StringTable &StrTable,
+ ArrayRef<StringTable::Offset> PrefixesTable,
+ ArrayRef<Info> OptionInfos,
+ ArrayRef<StringTable::Offset> PrefixesUnionOffsets,
+ bool IgnoreCase = false)
+ : PrecomputedOptTable(StrTable, PrefixesTable, OptionInfos, {}, {},
+ PrefixesUnionOffsets, IgnoreCase) {}
};
} // end namespace opt
@@ -452,33 +504,36 @@ class PrecomputedOptTable : public OptTable {
#define LLVM_MAKE_OPT_ID_WITH_ID_PREFIX( \
ID_PREFIX, PREFIXES_OFFSET, PREFIXED_NAME_OFFSET, ID, KIND, GROUP, ALIAS, \
ALIASARGS, FLAGS, VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, \
- METAVAR, VALUES) \
+ METAVAR, VALUES, COMMANDIDS_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, \
+ COMMANDIDS_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, COMMANDIDS_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, COMMANDIDS_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, \
+ COMMANDIDS_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, \
+ COMMANDIDS_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, COMMANDIDS_OFFSET)
#endif // LLVM_OPTION_OPTTABLE_H
diff --git a/llvm/lib/Option/ArgList.cpp b/llvm/lib/Option/ArgList.cpp
index c4188b3b12112..7c94e78c41e67 100644
--- a/llvm/lib/Option/ArgList.cpp
+++ b/llvm/lib/Option/ArgList.cpp
@@ -20,6 +20,7 @@
#include "llvm/Support/raw_ostream.h"
#include <algorithm>
#include <cassert>
+#include <cstddef>
#include <memory>
#include <string>
#include <utility>
@@ -202,6 +203,17 @@ void ArgList::print(raw_ostream &O) const {
LLVM_DUMP_METHOD void ArgList::dump() const { print(dbgs()); }
#endif
+StringRef ArgList::getSubcommand() const {
+ for (const Arg *A : *this) {
+ if (A->getOption().getKind() == Option::InputClass) {
+ if (StringRef(A->getValue()).empty())
+ return StringRef();
+ return A->getValue();
+ }
+ }
+ return StringRef();
+}
+
void InputArgList::releaseMemory() {
// An InputArgList always owns its arguments.
for (Arg *A : *this)
diff --git a/llvm/lib/Option/OptTable.cpp b/llvm/lib/Option/OptTable.cpp
index 6d10e6154147e..80c36407e3b70 100644
--- a/llvm/lib/Option/OptTable.cpp
+++ b/llvm/lib/Option/OptTable.cpp
@@ -80,8 +80,15 @@ OptSpecifier::OptSpecifier(const Option *Opt) : ID(Opt->getID()) {}
OptTable::OptTable(const StringTable &StrTable,
ArrayRef<StringTable::Offset> PrefixesTable,
ArrayRef<Info> OptionInfos, bool IgnoreCase)
+ : OptTable(StrTable, PrefixesTable, OptionInfos, {}, {}, IgnoreCase) {}
+
+OptTable::OptTable(const StringTable &StrTable,
+ ArrayRef<StringTable::Offset> PrefixesTable,
+ ArrayRef<Info> OptionInfos, ArrayRef<Command> Commands,
+ ArrayRef<unsigned> CommandIDsTable, bool IgnoreCase)
: StrTable(&StrTable), PrefixesTable(PrefixesTable),
- OptionInfos(OptionInfos), IgnoreCase(IgnoreCase) {
+ OptionInfos(OptionInfos), Commands(Commands),
+ CommandIDsTable(CommandIDsTable), IgnoreCase(IgnoreCase) {
// Explicitly zero initialize the error to work around a bug in array
// value-initialization on MinGW with gcc 4.3.5.
@@ -415,16 +422,18 @@ std::unique_ptr<Arg> OptTable::parseOneArgGrouped(InputArgList &Args,
std::unique_ptr<Arg> OptTable::ParseOneArg(const ArgList &Args, unsigned &Index,
Visibility VisibilityMask) const {
- return internalParseOneArg(Args, Index, [VisibilityMask](const Option &Opt) {
- return !Opt.hasVisibilityFlag(VisibilityMask);
- });
+ return internalParseOneArg(Args, Index, nullptr,
+ [VisibilityMask](const Option &Opt) {
+ return !Opt.hasVisibilityFlag(VisibilityMask);
+ });
}
std::unique_ptr<Arg> OptTable::ParseOneArg(const ArgList &Args, unsigned &Index,
unsigned FlagsToInclude,
unsigned FlagsToExclude) const {
return internalParseOneArg(
- Args, Index, [FlagsToInclude, FlagsToExclude](const Option &Opt) {
+ Args, Index, nullptr,
+ [FlagsToInclude, FlagsToExclude](const Option &Opt) {
if (FlagsToInclude && !Opt.hasFlag(FlagsToInclude))
return true;
if (Opt.hasFlag(FlagsToExclude))
@@ -434,7 +443,7 @@ std::unique_ptr<Arg> OptTable::ParseOneArg(const ArgList &Args, unsigned &Index,
}
std::unique_ptr<Arg> OptTable::internalParseOneArg(
- const ArgList &Args, unsigned &Index,
+ const ArgList &Args, unsigned &Index, const Command *ActiveCommand,
std::function<bool(const Option &)> ExcludeOption) const {
unsigned Prev = Index;
StringRef Str = Args.getArgString(Index);
@@ -476,6 +485,18 @@ std::unique_ptr<Arg> OptTable::internalParseOneArg(
if (ExcludeOption(Opt))
continue;
+ // If a command is active, accept options for that command.
+ if (ActiveCommand) {
+ unsigned ActiveCommandID = ActiveCommand - Commands.data();
+ ArrayRef<unsigned> CommandIDs = Start->getCommandIDs(CommandIDsTable);
+ bool IsInCommand = is_contained(CommandIDs, ActiveCommandID);
+ // Command ID 0 is the top level command.
+ bool IsGlobal = is_contained(CommandIDs, 0);
+ // If not part of the command and not a global option, continue.
+ if (!IsInCommand && !IsGlobal)
+ continue;
+ }
+
// See if this option matches.
if (std::unique_ptr<Arg> A =
Opt.accept(Args, StringRef(Args.getArgString(Index), ArgSize),
@@ -534,6 +555,21 @@ InputArgList OptTable::internalParseArgs(
MissingArgIndex = MissingArgCount = 0;
unsigned Index = 0, End = ArgArr.size();
+ const Command *ActiveCommand = nullptr;
+
+ // Look for subcommand which is positional.
+ if (!Commands.empty() && Index < End) {
+ StringRef FirstArg = Args.getArgString(Index);
+ if (isInput(PrefixesUnion, FirstArg)) {
+ for (const auto &C : Commands) {
+ if (FirstArg == C.Name) {
+ ActiveCommand = &C;
+ break;
+ }
+ }
+ }
+ }
+
while (Index < End) {
// Ingore nullptrs, they are response file's EOL markers
if (Args.getArgString(Index) == nullptr) {
@@ -558,9 +594,10 @@ InputArgList OptTable::internalParseArgs(
}
unsigned Prev = Index;
- std::unique_ptr<Arg> A = GroupedShortOptions
- ? parseOneArgGrouped(Args, Index)
- : internalParseOneArg(Args, Index, ExcludeOption);
+ std::unique_ptr<Arg> A =
+ GroupedShortOptions
+ ? parseOneArgGrouped(Args, Index)
+ : internalParseOneArg(Args, Index, ActiveCommand, ExcludeOption);
assert((Index > Prev || GroupedShortOptions) &&
"Parser failed to consume argument.");
@@ -715,9 +752,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 +768,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, {}, ShowHidden, ShowAllAliases,
[FlagsToInclude, FlagsToExclude](const Info &CandidateInfo) {
if (FlagsToInclude && !(CandidateInfo.Flags & FlagsToInclude))
return true;
@@ -742,8 +780,9 @@ 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";
@@ -751,6 +790,35 @@ void OptTable::internalPrintHelp(
// Render help text into a map of group-name to a list of (option, help)
// pairs.
std::map<std::string, std::vector<OptionInfo>> GroupedOptionHelp;
+ StringRef TopLevelCommandName = "TopLevelCommand";
+ if (Subcommand.empty()) {
+ // Assume top level command (toolname) by default.
+ Subcommand = TopLevelCommandName;
+ }
+
+ const Command *ActiveCommand = nullptr;
+ for (const auto &C : Commands) {
+ if (Subcommand == C.Name) {
+ ActiveCommand = &C;
+ if (ActiveCommand->HelpText)
+ OS << ActiveCommand->HelpText << "\n\n";
+ // TODO: Need to sortout how to maintain helptext for toplevel and
+ // subcommands and show them in view. What does existing tool do?
+ break;
+ }
+ }
+
+ if ((!ActiveCommand || ActiveCommand->Name == TopLevelCommandName) &&
+ Commands.size() > 1) {
+ OS << "SUBCOMMANDS:\n\n";
+ for (const auto &C : Commands) {
+ if (C.Name == TopLevelCommandName)
+ continue;
+ // TODO(prabhuk): This should be better aligned in UI using a helper
+ OS << C.Name << " - " << C.HelpText << "\n";
+ }
+ OS << "\n";
+ }
for (unsigned Id = 1, e = getNumOptions() + 1; Id != e; ++Id) {
// FIXME: Split out option groups.
@@ -764,6 +832,18 @@ void OptTable::internalPrintHelp(
if (ExcludeOption(CandidateInfo))
continue;
+ if (ActiveCommand) {
+ // ActiveCommand won't be set for tools that did not create command group
+ // info table.
+ // TODO: Move this to a lambda outside the loop.
+ ArrayRef<unsigned> CommandIDs =
+ CandidateInfo.getCommandIDs(CommandIDsTable);
+ unsigned ActiveCommandID = ActiveCommand - Commands.data();
+ bool IsInCommand = is_contained(CommandIDs, ActiveCommandID);
+ if (!IsInCommand)
+ 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 +871,12 @@ 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,
+ ArrayRef<Command> Commands,
+ ArrayRef<unsigned> CommandIDsTable,
+ bool IgnoreCase)
+ : OptTable(StrTable, PrefixesTable, OptionInfos, Commands, CommandIDsTable,
+ IgnoreCase) {
std::set<StringRef> TmpPrefixesUnion;
for (auto const &Info : OptionInfos.drop_front(FirstSearchableIndex))
diff --git a/llvm/unittests/Option/OptionMarshallingTest.cpp b/llvm/unittests/Option/OptionMarshallingTest.cpp
index 005144b91bf7f..1f17ad217e9ee 100644
--- a/llvm/unittests/Option/OptionMarshallingTest.cpp
+++ b/llvm/unittests/Option/OptionMarshallingTest.cpp
@@ -29,8 +29,9 @@ static const OptionWithMarshallingInfo MarshallingTable[] = {
#define OPTION_WITH_MARSHALLING( \
PREFIX_TYPE, PREFIXED_NAME_OFFSET, ID, KIND, GROUP, ALIAS, ALIASARGS, \
FLAGS, VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, METAVAR, VALUES, \
- SHOULD_PARSE, ALWAYS_EMIT, KEYPATH, DEFAULT_VALUE, IMPLIED_CHECK, \
- IMPLIED_VALUE, NORMALIZER, DENORMALIZER, MERGER, EXTRACTOR, TABLE_INDEX) \
+ COMMANDIDS_OFFSET, SHOULD_PARSE, ALWAYS_EMIT, KEYPATH, DEFAULT_VALUE, \
+ IMPLIED_CHECK, IMPLIED_VALUE, NORMALIZER, DENORMALIZER, MERGER, EXTRACTOR, \
+ TABLE_INDEX) \
{PREFIXED_NAME_OFFSET, #KEYPATH, #IMPLIED_CHECK, #IMPLIED_VALUE},
#include "Opts.inc"
#undef OPTION_WITH_MARSHALLING
diff --git a/llvm/utils/TableGen/OptionParserEmitter.cpp b/llvm/utils/TableGen/OptionParserEmitter.cpp
index a470fbbcadd58..a89688c9ff799 100644
--- a/llvm/utils/TableGen/OptionParserEmitter.cpp
+++ b/llvm/utils/TableGen/OptionParserEmitter.cpp
@@ -9,6 +9,7 @@
#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/Support/InterleavedRange.h"
@@ -258,6 +259,13 @@ 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 *> Commands =
+ Records.getAllDerivedDefinitions("Command");
+ // TopLevelCommand should come first.
+ std::stable_partition(Commands.begin(), Commands.end(), [](const Record *R) {
+ return R->getName() == "TopLevelCommand";
+ });
+
emitSourceFileHeader("Option Parsing Definitions", OS);
// Generate prefix groups.
@@ -271,6 +279,20 @@ static void emitOptionParser(const RecordKeeper &Records, raw_ostream &OS) {
Prefixes.try_emplace(PrefixKey, 0);
}
+ // Generate command groups.
+ typedef SmallVector<StringRef, 2> CommandKeyT;
+ typedef std::map<CommandKeyT, unsigned> CommandIDsT;
+ CommandIDsT CommandIDs;
+ CommandIDs.try_emplace(CommandKeyT(), 0);
+ for (const Record &R : llvm::make_pointee_range(Opts)) {
+ std::vector<const Record *> RCommands =
+ R.getValueAsListOfDefs("CommandGroup");
+ CommandKeyT CommandKey;
+ for (const auto &Command : RCommands)
+ CommandKey.push_back(Command->getName());
+ CommandIDs.try_emplace(CommandKey, 0);
+ }
+
DenseSet<StringRef> PrefixesUnionSet;
for (const auto &[Prefix, _] : Prefixes)
PrefixesUnionSet.insert_range(Prefix);
@@ -323,6 +345,39 @@ static void emitOptionParser(const RecordKeeper &Records, raw_ostream &OS) {
OS << "\n};\n";
OS << "#endif // OPTTABLE_PREFIXES_TABLE_CODE\n\n";
+ // Dump command IDs.
+ OS << "/////////";
+ OS << "// Command IDs\n\n";
+ OS << "#ifdef OPTTABLE_COMMAND_IDS_TABLE_CODE\n";
+ OS << "static constexpr unsigned OptionCommandIDsTable[] = {\n";
+ {
+ // Ensure the first command set is always empty.
+ assert(!CommandIDs.empty() &&
+ "We should always emit an empty set of commands");
+ assert(CommandIDs.begin()->first.empty() &&
+ "First command set should always be empty");
+ llvm::ListSeparator Sep(",\n");
+ unsigned CurIndex = 0;
+ for (auto &[Command, CommandIndex] : CommandIDs) {
+ // First emit the number of command strings in this list of commands.
+ OS << Sep << " " << Command.size() << " /* commands */";
+ CommandIndex = CurIndex;
+ assert((CurIndex == 0 || !Command.empty()) &&
+ "Only first command set should be empty!");
+ for (const auto &CommandKey : Command) {
+ auto It = llvm::find_if(Commands, [&](const Record *R) {
+ return R->getName() == CommandKey;
+ });
+ assert(It != Commands.end() && "Command not found");
+ OS << ", " << std::distance(Commands.begin(), It) << " /* '"
+ << CommandKey << "' */";
+ }
+ CurIndex += Command.size() + 1;
+ }
+ }
+ OS << "\n};\n";
+ OS << "#endif // OPTTABLE_COMMAND_IDS_TABLE_CODE\n\n";
+
// Dump prefixes union.
OS << "/////////\n";
OS << "// Prefix Union\n\n";
@@ -400,7 +455,22 @@ static void emitOptionParser(const RecordKeeper &Records, raw_ostream &OS) {
OS << ", nullptr";
// The option Values (unused for groups).
- OS << ", nullptr)\n";
+ OS << ", nullptr";
+
+ // The option CommandIDsOffset.
+ OS << ", ";
+ if (R.getValue("CommandGroup") != nullptr) {
+ std::vector<const Record *> CommandGroup =
+ R.getValueAsListOfDefs("CommandGroup");
+ CommandKeyT CommandKey;
+ for (const auto &Command : CommandGroup)
+ CommandKey.push_back(Command->getName());
+ OS << CommandIDs[CommandKey];
+ } else {
+ // The option CommandIDsOffset (for default top level toolname is 0).
+ OS << " 0";
+ }
+ OS << ")\n";
}
OS << "\n";
@@ -527,6 +597,20 @@ static void emitOptionParser(const RecordKeeper &Records, raw_ostream &OS) {
OS << getOptionName(R) << "_Values";
else
OS << "nullptr";
+
+ // The option CommandIDsOffset.
+ OS << ", ";
+ if (R.getValue("CommandGroup") != nullptr) {
+ std::vector<const Record *> CommandGroup =
+ R.getValueAsListOfDefs("CommandGroup");
+ CommandKeyT CommandKey;
+ for (const auto &Command : CommandGroup)
+ CommandKey.push_back(Command->getName());
+ OS << CommandIDs[CommandKey];
+ } else {
+ // The option CommandIDsOffset (for default top level toolname is 0).
+ OS << " 0";
+ }
};
auto IsMarshallingOption = [](const Record &R) {
@@ -595,6 +679,19 @@ static void emitOptionParser(const RecordKeeper &Records, raw_ostream &OS) {
OS << "#endif // SIMPLE_ENUM_VALUE_TABLE\n";
OS << "\n";
+ OS << "/////////\n";
+ OS << "\n// Commands\n\n";
+ OS << "#ifdef OPTTABLE_COMMANDS_CODE\n";
+ OS << "static constexpr llvm::opt::OptTable::Command OptionCommands[] = {\n";
+ for (const Record *Command : Commands) {
+ OS << " { \"" << Command->getValueAsString("Name") << "\", ";
+ if (Command->isSubClassOf("Subcommand"))
+ OS << "\"" << Command->getValueAsString("HelpText") << "\" },\n";
+ else
+ OS << "nullptr },\n";
+ }
+ OS << "};\n";
+ OS << "#endif // OPTTABLE_COMMANDS_CODE\n\n";
OS << "\n";
}
>From 8665d9eea63c69de644e582afc3fa2a3bf240295 Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Fri, 22 Aug 2025 22:04:00 +0000
Subject: [PATCH 02/10] Cleanup internalPrintHelp code
---
llvm/lib/Option/OptTable.cpp | 73 ++++++++++++++++++------------------
1 file changed, 37 insertions(+), 36 deletions(-)
diff --git a/llvm/lib/Option/OptTable.cpp b/llvm/lib/Option/OptTable.cpp
index 80c36407e3b70..d8fcbb9193a6e 100644
--- a/llvm/lib/Option/OptTable.cpp
+++ b/llvm/lib/Option/OptTable.cpp
@@ -779,6 +779,15 @@ void OptTable::printHelp(raw_ostream &OS, const char *Usage, const char *Title,
Visibility(0));
}
+static const OptTable::Command *
+getActiveCommand(ArrayRef<OptTable::Command> Commands, StringRef Subcommand) {
+ for (const auto &C : Commands) {
+ if (Subcommand == C.Name)
+ return &C;
+ }
+ return nullptr;
+}
+
void OptTable::internalPrintHelp(
raw_ostream &OS, const char *Usage, const char *Title, StringRef Subcommand,
bool ShowHidden, bool ShowAllAliases,
@@ -790,35 +799,36 @@ void OptTable::internalPrintHelp(
// Render help text into a map of group-name to a list of (option, help)
// pairs.
std::map<std::string, std::vector<OptionInfo>> GroupedOptionHelp;
- StringRef TopLevelCommandName = "TopLevelCommand";
- if (Subcommand.empty()) {
- // Assume top level command (toolname) by default.
- Subcommand = TopLevelCommandName;
- }
- const Command *ActiveCommand = nullptr;
- for (const auto &C : Commands) {
- if (Subcommand == C.Name) {
- ActiveCommand = &C;
- if (ActiveCommand->HelpText)
- OS << ActiveCommand->HelpText << "\n\n";
- // TODO: Need to sortout how to maintain helptext for toplevel and
- // subcommands and show them in view. What does existing tool do?
- break;
+ const Command *ActiveCommand = getActiveCommand(Commands, Subcommand);
+ if (ActiveCommand) {
+ if (ActiveCommand->HelpText)
+ OS << ActiveCommand->HelpText << "\n\n";
+ } else {
+ // Assume top level command (toolname) is active.
+ StringRef TopLevelCommandName = "TopLevelCommand";
+ if (Commands.size() > 1) {
+ OS << "SUBCOMMANDS:\n\n";
+ for (const auto &C : Commands) {
+ if (C.Name == TopLevelCommandName)
+ continue;
+ OS << C.Name << " - " << C.HelpText << "\n";
+ }
+ OS << "\n";
}
}
- if ((!ActiveCommand || ActiveCommand->Name == TopLevelCommandName) &&
- Commands.size() > 1) {
- OS << "SUBCOMMANDS:\n\n";
- for (const auto &C : Commands) {
- if (C.Name == TopLevelCommandName)
- continue;
- // TODO(prabhuk): This should be better aligned in UI using a helper
- OS << C.Name << " - " << C.HelpText << "\n";
- }
- OS << "\n";
- }
+ auto DoesOptionBelongToActiveCommand =
+ [this, &ActiveCommand](const Info &CandidateInfo) {
+ // ActiveCommand won't be set for tools that did not create command
+ // group info table.
+ if (!ActiveCommand)
+ return true;
+ ArrayRef<unsigned> CommandIDs =
+ CandidateInfo.getCommandIDs(CommandIDsTable);
+ unsigned ActiveCommandID = ActiveCommand - Commands.data();
+ return is_contained(CommandIDs, ActiveCommandID);
+ };
for (unsigned Id = 1, e = getNumOptions() + 1; Id != e; ++Id) {
// FIXME: Split out option groups.
@@ -832,17 +842,8 @@ void OptTable::internalPrintHelp(
if (ExcludeOption(CandidateInfo))
continue;
- if (ActiveCommand) {
- // ActiveCommand won't be set for tools that did not create command group
- // info table.
- // TODO: Move this to a lambda outside the loop.
- ArrayRef<unsigned> CommandIDs =
- CandidateInfo.getCommandIDs(CommandIDsTable);
- unsigned ActiveCommandID = ActiveCommand - Commands.data();
- bool IsInCommand = is_contained(CommandIDs, ActiveCommandID);
- if (!IsInCommand)
- continue;
- }
+ if (!DoesOptionBelongToActiveCommand(CandidateInfo))
+ continue;
// If an alias doesn't have a help text, show a help text for the aliased
// option instead.
>From d485046726b79d202848f2bac049f33684ff01af Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Fri, 22 Aug 2025 22:05:59 +0000
Subject: [PATCH 03/10] Fix formatting
---
llvm/include/llvm/Option/OptParser.td | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/include/llvm/Option/OptParser.td b/llvm/include/llvm/Option/OptParser.td
index 2bf920be8d946..9d3b660b6312c 100644
--- a/llvm/include/llvm/Option/OptParser.td
+++ b/llvm/include/llvm/Option/OptParser.td
@@ -296,4 +296,4 @@ class ValueExtractor<code extractor> { code ValueExtractor = extractor; }
def INPUT : Option<[], "<input>", KIND_INPUT, [TopLevelCommand]>;
def UNKNOWN : Option<[], "<unknown>", KIND_UNKNOWN, [TopLevelCommand]>;
-#endif // LLVM_OPTION_OPTPARSER_TD
\ No newline at end of file
+#endif // LLVM_OPTION_OPTPARSER_TD
>From b325c781bb68e24e09a534c31efc1a5663b7ef16 Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Fri, 22 Aug 2025 23:02:50 +0000
Subject: [PATCH 04/10] Add custom usage string to tablegen for each subcommand
---
llvm/examples/OptSubcommand/Opts.td | 9 ++++++---
llvm/include/llvm/Option/OptParser.td | 3 ++-
llvm/include/llvm/Option/OptTable.h | 1 +
llvm/lib/Option/OptTable.cpp | 6 +++---
llvm/utils/TableGen/OptionParserEmitter.cpp | 9 +++++----
5 files changed, 17 insertions(+), 11 deletions(-)
diff --git a/llvm/examples/OptSubcommand/Opts.td b/llvm/examples/OptSubcommand/Opts.td
index 8c4f66b8c3043..a724fb739ec72 100644
--- a/llvm/examples/OptSubcommand/Opts.td
+++ b/llvm/examples/OptSubcommand/Opts.td
@@ -1,10 +1,13 @@
include "llvm/Option/OptParser.td"
-def sc_foo : Subcommand<"foo", "HelpText for Subcommand foo.">;
+def sc_foo : Subcommand<"foo", "HelpText for Subcommand foo.",
+ "OptSubcommand foo <options>">;
-def sc_bar : Subcommand<"bar", "HelpText for Subcommand bar.">;
+def sc_bar : Subcommand<"bar", "HelpText for Subcommand bar.",
+ "OptSubcommand bar <options>">;
-def help : Flag<["--"], "help">, HelpText<"Top Level Help Text for the tool.">;
+def help : Flag<["--"], "help">,
+ HelpText<"OptSubcommand <subcommand> <options>">;
def version : Flag<["-"], "version">,
HelpText<"Toplevel Display the version number">;
diff --git a/llvm/include/llvm/Option/OptParser.td b/llvm/include/llvm/Option/OptParser.td
index 9d3b660b6312c..ddcf9de0d9fe1 100644
--- a/llvm/include/llvm/Option/OptParser.td
+++ b/llvm/include/llvm/Option/OptParser.td
@@ -102,8 +102,9 @@ class HelpTextVariant<list<OptionVisibility> visibilities, string text> {
class Command<string name> { string Name = name; }
// Class definition for positional subcommands.
-class Subcommand<string name, string helpText> : Command<name> {
+class Subcommand<string name, string helpText, string usage> : Command<name> {
string HelpText = helpText;
+ string Usage = usage;
}
// Compile time representation for top level command (aka toolname).
diff --git a/llvm/include/llvm/Option/OptTable.h b/llvm/include/llvm/Option/OptTable.h
index 3f362521a7e64..911eb4ae93066 100644
--- a/llvm/include/llvm/Option/OptTable.h
+++ b/llvm/include/llvm/Option/OptTable.h
@@ -58,6 +58,7 @@ class LLVM_ABI OptTable {
struct Command {
const char *Name;
const char *HelpText;
+ const char *Usage;
};
/// Entry for a single option instance in the option data table.
diff --git a/llvm/lib/Option/OptTable.cpp b/llvm/lib/Option/OptTable.cpp
index d8fcbb9193a6e..af3526cd24331 100644
--- a/llvm/lib/Option/OptTable.cpp
+++ b/llvm/lib/Option/OptTable.cpp
@@ -794,7 +794,6 @@ void OptTable::internalPrintHelp(
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.
@@ -802,9 +801,10 @@ void OptTable::internalPrintHelp(
const Command *ActiveCommand = getActiveCommand(Commands, Subcommand);
if (ActiveCommand) {
- if (ActiveCommand->HelpText)
- OS << ActiveCommand->HelpText << "\n\n";
+ OS << ActiveCommand->HelpText << "\n\n";
+ OS << "USAGE: " << ActiveCommand->Usage << "\n\n";
} else {
+ OS << "USAGE: " << Usage << "\n\n";
// Assume top level command (toolname) is active.
StringRef TopLevelCommandName = "TopLevelCommand";
if (Commands.size() > 1) {
diff --git a/llvm/utils/TableGen/OptionParserEmitter.cpp b/llvm/utils/TableGen/OptionParserEmitter.cpp
index a89688c9ff799..53683f7ef4d30 100644
--- a/llvm/utils/TableGen/OptionParserEmitter.cpp
+++ b/llvm/utils/TableGen/OptionParserEmitter.cpp
@@ -685,10 +685,11 @@ static void emitOptionParser(const RecordKeeper &Records, raw_ostream &OS) {
OS << "static constexpr llvm::opt::OptTable::Command OptionCommands[] = {\n";
for (const Record *Command : Commands) {
OS << " { \"" << Command->getValueAsString("Name") << "\", ";
- if (Command->isSubClassOf("Subcommand"))
- OS << "\"" << Command->getValueAsString("HelpText") << "\" },\n";
- else
- OS << "nullptr },\n";
+ if (Command->isSubClassOf("Subcommand")) {
+ OS << "\"" << Command->getValueAsString("HelpText") << "\", ";
+ OS << "\"" << Command->getValueAsString("Usage") << "\" },\n";
+ } else
+ OS << "nullptr, nullptr},\n";
}
OS << "};\n";
OS << "#endif // OPTTABLE_COMMANDS_CODE\n\n";
>From 61334ef88badc3088758a0955738d89a7254f1cd Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Fri, 22 Aug 2025 23:05:41 +0000
Subject: [PATCH 05/10] Add todo
---
llvm/include/llvm/Option/OptParser.td | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/include/llvm/Option/OptParser.td b/llvm/include/llvm/Option/OptParser.td
index ddcf9de0d9fe1..dba529bd65841 100644
--- a/llvm/include/llvm/Option/OptParser.td
+++ b/llvm/include/llvm/Option/OptParser.td
@@ -104,7 +104,7 @@ class Command<string name> { string Name = name; }
// Class definition for positional subcommands.
class Subcommand<string name, string helpText, string usage> : Command<name> {
string HelpText = helpText;
- string Usage = usage;
+ string Usage = usage; //TODO(prabhuk): This could be part of another subclass of subcommand to make passing usage string optional.
}
// Compile time representation for top level command (aka toolname).
>From 1690f3b5db41e9b375b188eee7179ae5996e9cbd Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Tue, 26 Aug 2025 16:27:25 +0000
Subject: [PATCH 06/10] Make subcommand usage text optional
---
llvm/examples/OptSubcommand/Opts.td | 3 +--
llvm/include/llvm/Option/OptParser.td | 3 ++-
llvm/lib/Option/OptTable.cpp | 3 ++-
3 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/llvm/examples/OptSubcommand/Opts.td b/llvm/examples/OptSubcommand/Opts.td
index a724fb739ec72..21a417ffec12a 100644
--- a/llvm/examples/OptSubcommand/Opts.td
+++ b/llvm/examples/OptSubcommand/Opts.td
@@ -1,7 +1,6 @@
include "llvm/Option/OptParser.td"
-def sc_foo : Subcommand<"foo", "HelpText for Subcommand foo.",
- "OptSubcommand foo <options>">;
+def sc_foo : Subcommand<"foo", "HelpText for Subcommand foo.">;
def sc_bar : Subcommand<"bar", "HelpText for Subcommand bar.",
"OptSubcommand bar <options>">;
diff --git a/llvm/include/llvm/Option/OptParser.td b/llvm/include/llvm/Option/OptParser.td
index dba529bd65841..365228d41711a 100644
--- a/llvm/include/llvm/Option/OptParser.td
+++ b/llvm/include/llvm/Option/OptParser.td
@@ -102,7 +102,8 @@ class HelpTextVariant<list<OptionVisibility> visibilities, string text> {
class Command<string name> { string Name = name; }
// Class definition for positional subcommands.
-class Subcommand<string name, string helpText, string usage> : Command<name> {
+class Subcommand<string name, string helpText, string usage = "">
+ : Command<name> {
string HelpText = helpText;
string Usage = usage; //TODO(prabhuk): This could be part of another subclass of subcommand to make passing usage string optional.
}
diff --git a/llvm/lib/Option/OptTable.cpp b/llvm/lib/Option/OptTable.cpp
index af3526cd24331..e51ae39ffa544 100644
--- a/llvm/lib/Option/OptTable.cpp
+++ b/llvm/lib/Option/OptTable.cpp
@@ -802,7 +802,8 @@ void OptTable::internalPrintHelp(
const Command *ActiveCommand = getActiveCommand(Commands, Subcommand);
if (ActiveCommand) {
OS << ActiveCommand->HelpText << "\n\n";
- OS << "USAGE: " << ActiveCommand->Usage << "\n\n";
+ if (!StringRef(ActiveCommand->Usage).empty())
+ OS << "USAGE: " << ActiveCommand->Usage << "\n\n";
} else {
OS << "USAGE: " << Usage << "\n\n";
// Assume top level command (toolname) is active.
>From 9c419d9f064fcf1234d2b863836a6c156c56b77d Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Tue, 26 Aug 2025 15:13:29 -0700
Subject: [PATCH 07/10] Make subcommand's position flexible and not just as
first argument.
---
.../examples/OptSubcommand/llvm-hello-sub.cpp | 51 ++++++++++++-------
llvm/include/llvm/Option/ArgList.h | 8 ++-
llvm/include/llvm/Option/OptTable.h | 2 +
llvm/lib/Option/ArgList.cpp | 20 ++++++--
llvm/lib/Option/OptTable.cpp | 6 ++-
5 files changed, 62 insertions(+), 25 deletions(-)
diff --git a/llvm/examples/OptSubcommand/llvm-hello-sub.cpp b/llvm/examples/OptSubcommand/llvm-hello-sub.cpp
index aecc981e485c1..c6f3c1e0be754 100644
--- a/llvm/examples/OptSubcommand/llvm-hello-sub.cpp
+++ b/llvm/examples/OptSubcommand/llvm-hello-sub.cpp
@@ -1,6 +1,7 @@
#include "llvm/ADT/ArrayRef.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"
@@ -54,40 +55,54 @@ int main(int argc, char **argv) {
InputArgList Args = T.ParseArgs(ArrayRef(argv + 1, argc - 1), MissingArgIndex,
MissingArgCount);
- StringRef Subcommand = Args.getSubcommand();
+ auto SubcommandResult = Args.getSubcommand(T.getCommands());
+ if (!SubcommandResult) {
+ llvm::errs() << "error: unknown subcommand '"
+ << toString(SubcommandResult.takeError())
+ << "'. See --help.\n";
+ return 1;
+ }
+ // Valid subcommand found.
+ StringRef Subcommand = *SubcommandResult;
+
+ // 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;
}
-
- if (Args.hasArg(OPT_version)) {
- llvm::outs() << "LLVM Hello Subcommand Example 1.0\n";
- return 0;
+ bool HasUnknownOptions = false;
+ for (const Arg *A : Args.filtered(OPT_UNKNOWN)) {
+ HasUnknownOptions = true;
+ llvm::errs() << "Unknown option `" << A->getAsString(Args) << "'\n";
}
-
- if (Subcommand == "foo") {
+ 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 (Args.hasArg(OPT_uppercase))
llvm::outs() << "FOO\n";
else if (Args.hasArg(OPT_lowercase))
llvm::outs() << "foo\n";
- else
- llvm::errs() << "error: unknown option for subcommand '" << Subcommand
- << "'. See -help.\n";
- return 1;
+
+ if (Args.hasArg(OPT_version))
+ llvm::outs() << "LLVM Hello Subcommand foo Example 1.0\n";
+
} else if (Subcommand == "bar") {
if (Args.hasArg(OPT_lowercase))
llvm::outs() << "bar\n";
else if (Args.hasArg(OPT_uppercase))
llvm::outs() << "BAR\n";
- else
- llvm::errs() << "error: unknown option for subcommand '" << Subcommand
- << "'. See -help.\n";
- } else {
- llvm::errs() << "error: unknown subcommand '" << Subcommand
- << "'. See --help.\n";
- return 1;
+
+ if (Args.hasArg(OPT_version))
+ llvm::outs() << "LLVM Hello Subcommand bar Example 1.0\n";
}
return 0;
diff --git a/llvm/include/llvm/Option/ArgList.h b/llvm/include/llvm/Option/ArgList.h
index 2394b2e8301b8..98b724412102d 100644
--- a/llvm/include/llvm/Option/ArgList.h
+++ b/llvm/include/llvm/Option/ArgList.h
@@ -20,6 +20,7 @@
#include "llvm/Option/OptSpecifier.h"
#include "llvm/Option/Option.h"
#include "llvm/Support/Compiler.h"
+#include "llvm/Support/Error.h"
#include <algorithm>
#include <cstddef>
#include <initializer_list>
@@ -280,8 +281,11 @@ class ArgList {
/// list.
virtual unsigned getNumInputArgStrings() const = 0;
- /// getSubcommand - Return the active subcommand, if one exists.
- LLVM_ABI StringRef getSubcommand() const;
+ /// getSubcommand - Return the active subcommand, if one exists. Return empty
+ /// StringRef if no subcommand passed. Return an error if an invalid/unknown
+ /// subcommand is found as the first argument.
+ LLVM_ABI llvm::Expected<StringRef>
+ getSubcommand(const ArrayRef<OptTable::Command> Commands) const;
/// @}
/// @name Argument Lookup Utilities
diff --git a/llvm/include/llvm/Option/OptTable.h b/llvm/include/llvm/Option/OptTable.h
index 911eb4ae93066..ac94bdf17d8a8 100644
--- a/llvm/include/llvm/Option/OptTable.h
+++ b/llvm/include/llvm/Option/OptTable.h
@@ -212,6 +212,8 @@ class LLVM_ABI OptTable {
/// Return the string table used for option names.
const StringTable &getStrTable() const { return *StrTable; }
+ const ArrayRef<Command> getCommands() const { return Commands; }
+
/// Return the prefixes table used for option names.
ArrayRef<StringTable::Offset> getPrefixesTable() const {
return PrefixesTable;
diff --git a/llvm/lib/Option/ArgList.cpp b/llvm/lib/Option/ArgList.cpp
index 7c94e78c41e67..418052bf202f7 100644
--- a/llvm/lib/Option/ArgList.cpp
+++ b/llvm/lib/Option/ArgList.cpp
@@ -14,9 +14,11 @@
#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/Error.h"
#include "llvm/Support/raw_ostream.h"
#include <algorithm>
#include <cassert>
@@ -203,14 +205,24 @@ void ArgList::print(raw_ostream &O) const {
LLVM_DUMP_METHOD void ArgList::dump() const { print(dbgs()); }
#endif
-StringRef ArgList::getSubcommand() const {
+llvm::Expected<StringRef>
+ArgList::getSubcommand(const ArrayRef<OptTable::Command> Commands) const {
+ // Identify if a valid subcommand is passed.
for (const Arg *A : *this) {
if (A->getOption().getKind() == Option::InputClass) {
- if (StringRef(A->getValue()).empty())
- return StringRef();
- return A->getValue();
+ assert(!StringRef(A->getValue()).empty());
+ for (auto CMD : Commands) {
+ if (StringRef(CMD.Name) == "TopLevelCommand")
+ continue;
+ if (StringRef(CMD.Name) == A->getValue())
+ return A->getValue(); // Found a valid subcommand.
+ }
+ // Invalid/Unexpected subcommand passed. Let the users handle this case as
+ // they see fit. return
+ // llvm::createStringError(std::errc::invalid_argument, A->getValue());
}
}
+ // No registered subcommand found.
return StringRef();
}
diff --git a/llvm/lib/Option/OptTable.cpp b/llvm/lib/Option/OptTable.cpp
index e51ae39ffa544..e223bf64715ba 100644
--- a/llvm/lib/Option/OptTable.cpp
+++ b/llvm/lib/Option/OptTable.cpp
@@ -810,9 +810,13 @@ void OptTable::internalPrintHelp(
StringRef TopLevelCommandName = "TopLevelCommand";
if (Commands.size() > 1) {
OS << "SUBCOMMANDS:\n\n";
+ // This loop prints subcommands list and sets ActiveCommand to
+ // TopLevelCommand while iterating over all commands.
for (const auto &C : Commands) {
- if (C.Name == TopLevelCommandName)
+ if (C.Name == TopLevelCommandName) {
+ ActiveCommand = &C;
continue;
+ }
OS << C.Name << " - " << C.HelpText << "\n";
}
OS << "\n";
>From 19e643754ca3455f9be1be14a5685540fbf4ea53 Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Tue, 26 Aug 2025 15:22:44 -0700
Subject: [PATCH 08/10] Handle unknown subcommands in a graceful manner.
---
llvm/lib/Option/ArgList.cpp | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/llvm/lib/Option/ArgList.cpp b/llvm/lib/Option/ArgList.cpp
index 418052bf202f7..b963d9f791654 100644
--- a/llvm/lib/Option/ArgList.cpp
+++ b/llvm/lib/Option/ArgList.cpp
@@ -218,8 +218,9 @@ ArgList::getSubcommand(const ArrayRef<OptTable::Command> Commands) const {
return A->getValue(); // Found a valid subcommand.
}
// Invalid/Unexpected subcommand passed. Let the users handle this case as
- // they see fit. return
- // llvm::createStringError(std::errc::invalid_argument, A->getValue());
+ // they see fit.
+ return llvm::createStringError(std::errc::invalid_argument,
+ A->getValue());
}
}
// No registered subcommand found.
>From 8d5363b2d700fb22750a330a6591ab1c6725850b Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Tue, 26 Aug 2025 16:07:23 -0700
Subject: [PATCH 09/10] Handle more than one subcommand passed in commandline.
---
.../examples/OptSubcommand/llvm-hello-sub.cpp | 4 +--
llvm/lib/Option/ArgList.cpp | 29 ++++++++++++++-----
2 files changed, 23 insertions(+), 10 deletions(-)
diff --git a/llvm/examples/OptSubcommand/llvm-hello-sub.cpp b/llvm/examples/OptSubcommand/llvm-hello-sub.cpp
index c6f3c1e0be754..3741e95b177ea 100644
--- a/llvm/examples/OptSubcommand/llvm-hello-sub.cpp
+++ b/llvm/examples/OptSubcommand/llvm-hello-sub.cpp
@@ -57,9 +57,9 @@ int main(int argc, char **argv) {
auto SubcommandResult = Args.getSubcommand(T.getCommands());
if (!SubcommandResult) {
- llvm::errs() << "error: unknown subcommand '"
+ llvm::errs() << "error: more than one subcommand passed ["
<< toString(SubcommandResult.takeError())
- << "'. See --help.\n";
+ << "]. See --help.\n";
return 1;
}
// Valid subcommand found.
diff --git a/llvm/lib/Option/ArgList.cpp b/llvm/lib/Option/ArgList.cpp
index b963d9f791654..240d05905392c 100644
--- a/llvm/lib/Option/ArgList.cpp
+++ b/llvm/lib/Option/ArgList.cpp
@@ -208,23 +208,36 @@ LLVM_DUMP_METHOD void ArgList::dump() const { print(dbgs()); }
llvm::Expected<StringRef>
ArgList::getSubcommand(const ArrayRef<OptTable::Command> Commands) const {
// Identify if a valid subcommand is passed.
+ StringRef SubCommand = {};
for (const Arg *A : *this) {
if (A->getOption().getKind() == Option::InputClass) {
assert(!StringRef(A->getValue()).empty());
for (auto CMD : Commands) {
if (StringRef(CMD.Name) == "TopLevelCommand")
continue;
- if (StringRef(CMD.Name) == A->getValue())
- return A->getValue(); // Found a valid subcommand.
+ if (StringRef(CMD.Name) == A->getValue()) {
+ // Found a valid subcommand.
+ if (SubCommand.empty()) {
+ SubCommand = A->getValue();
+ } else {
+ SmallString<20> SubCommandsList;
+ SubCommandsList += SubCommand;
+ SubCommandsList += ",";
+ SubCommandsList += A->getValue();
+ // More than one subcommand passed in commandline.
+ return llvm::createStringError(std::errc::invalid_argument,
+ SubCommandsList.c_str());
+ }
+ }
+ }
+ if (SubCommand.empty()) {
+ // TODO:: Return a different error for unknown subcommand
+ return llvm::createStringError(std::errc::invalid_argument,
+ A->getValue());
}
- // Invalid/Unexpected subcommand passed. Let the users handle this case as
- // they see fit.
- return llvm::createStringError(std::errc::invalid_argument,
- A->getValue());
}
}
- // No registered subcommand found.
- return StringRef();
+ return SubCommand;
}
void InputArgList::releaseMemory() {
>From 51ef95ecc6396eab3a3157ac4facab43f4295f54 Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Tue, 26 Aug 2025 17:53:04 -0700
Subject: [PATCH 10/10] Handle subcommand errors in parsing through lambdas.
---
.../examples/OptSubcommand/llvm-hello-sub.cpp | 36 +++++++++++-----
llvm/include/llvm/Option/ArgList.h | 10 ++---
llvm/lib/Option/ArgList.cpp | 43 ++++++++++---------
3 files changed, 53 insertions(+), 36 deletions(-)
diff --git a/llvm/examples/OptSubcommand/llvm-hello-sub.cpp b/llvm/examples/OptSubcommand/llvm-hello-sub.cpp
index 3741e95b177ea..e5d88048240f9 100644
--- a/llvm/examples/OptSubcommand/llvm-hello-sub.cpp
+++ b/llvm/examples/OptSubcommand/llvm-hello-sub.cpp
@@ -1,4 +1,5 @@
#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"
@@ -52,19 +53,34 @@ int main(int argc, char **argv) {
InitLLVM X(argc, argv);
HelloSubOptTable T;
unsigned MissingArgIndex, MissingArgCount;
+
+ auto HandleMultipleSubcommands = [](const 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 = [](const 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);
- auto SubcommandResult = Args.getSubcommand(T.getCommands());
- if (!SubcommandResult) {
- llvm::errs() << "error: more than one subcommand passed ["
- << toString(SubcommandResult.takeError())
- << "]. See --help.\n";
- return 1;
- }
- // Valid subcommand found.
- StringRef Subcommand = *SubcommandResult;
-
+ StringRef Subcommand = Args.getSubcommand(
+ T.getCommands(), HandleMultipleSubcommands, HandleOtherPositionals);
// Handle help. When help options is found, ignore all other options and exit
// after printing help.
if (Args.hasArg(OPT_help)) {
diff --git a/llvm/include/llvm/Option/ArgList.h b/llvm/include/llvm/Option/ArgList.h
index 98b724412102d..0bee87a10759d 100644
--- a/llvm/include/llvm/Option/ArgList.h
+++ b/llvm/include/llvm/Option/ArgList.h
@@ -281,11 +281,11 @@ class ArgList {
/// list.
virtual unsigned getNumInputArgStrings() const = 0;
- /// getSubcommand - Return the active subcommand, if one exists. Return empty
- /// StringRef if no subcommand passed. Return an error if an invalid/unknown
- /// subcommand is found as the first argument.
- LLVM_ABI llvm::Expected<StringRef>
- getSubcommand(const ArrayRef<OptTable::Command> Commands) const;
+ /// getSubcommand - Return the active subcommand, if one exists.
+ LLVM_ABI StringRef getSubcommand(
+ const ArrayRef<OptTable::Command> Commands,
+ std::function<void(ArrayRef<StringRef>)> HandleMultipleSubcommands,
+ std::function<void(ArrayRef<StringRef>)> HandleOtherPositionals) const;
/// @}
/// @name Argument Lookup Utilities
diff --git a/llvm/lib/Option/ArgList.cpp b/llvm/lib/Option/ArgList.cpp
index 240d05905392c..e1910c449ca99 100644
--- a/llvm/lib/Option/ArgList.cpp
+++ b/llvm/lib/Option/ArgList.cpp
@@ -205,38 +205,39 @@ void ArgList::print(raw_ostream &O) const {
LLVM_DUMP_METHOD void ArgList::dump() const { print(dbgs()); }
#endif
-llvm::Expected<StringRef>
-ArgList::getSubcommand(const ArrayRef<OptTable::Command> Commands) const {
- // Identify if a valid subcommand is passed.
+StringRef ArgList::getSubcommand(
+ const ArrayRef<OptTable::Command> Commands,
+ std::function<void(ArrayRef<StringRef>)> HandleMultipleSubcommands,
+ std::function<void(ArrayRef<StringRef>)> HandleOtherPositionals) const {
+
StringRef SubCommand = {};
+ SmallVector<StringRef, 4> SubCommands;
+ SmallVector<StringRef, 4> OtherPositionals;
for (const Arg *A : *this) {
+ bool IsSubCommand = false;
if (A->getOption().getKind() == Option::InputClass) {
- assert(!StringRef(A->getValue()).empty());
- for (auto CMD : Commands) {
+ for (const OptTable::Command CMD : Commands) {
if (StringRef(CMD.Name) == "TopLevelCommand")
continue;
if (StringRef(CMD.Name) == A->getValue()) {
- // Found a valid subcommand.
- if (SubCommand.empty()) {
- SubCommand = A->getValue();
- } else {
- SmallString<20> SubCommandsList;
- SubCommandsList += SubCommand;
- SubCommandsList += ",";
- SubCommandsList += A->getValue();
- // More than one subcommand passed in commandline.
- return llvm::createStringError(std::errc::invalid_argument,
- SubCommandsList.c_str());
- }
+ SubCommands.push_back(A->getValue());
+ IsSubCommand = true;
}
}
- if (SubCommand.empty()) {
- // TODO:: Return a different error for unknown subcommand
- return llvm::createStringError(std::errc::invalid_argument,
- A->getValue());
+ if (!IsSubCommand) {
+ OtherPositionals.push_back(A->getValue());
+ IsSubCommand = false;
}
}
}
+ if (SubCommands.size() > 1) {
+ HandleMultipleSubcommands(SubCommands);
+ }
+ if (!OtherPositionals.empty()) {
+ HandleOtherPositionals(OtherPositionals);
+ }
+ if (SubCommands.size() == 1)
+ return SubCommands.front();
return SubCommand;
}
More information about the llvm-commits
mailing list