Skip to content
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
3584c6a
[OptTable] Subcommand support.
Prabhuk Aug 19, 2025
8665d9e
Cleanup internalPrintHelp code
Prabhuk Aug 22, 2025
d485046
Fix formatting
Prabhuk Aug 22, 2025
b325c78
Add custom usage string to tablegen for each subcommand
Prabhuk Aug 22, 2025
61334ef
Add todo
Prabhuk Aug 22, 2025
1690f3b
Make subcommand usage text optional
Prabhuk Aug 26, 2025
9c419d9
Make subcommand's position flexible and not just as first argument.
Prabhuk Aug 26, 2025
19e6437
Handle unknown subcommands in a graceful manner.
Prabhuk Aug 26, 2025
8d5363b
Handle more than one subcommand passed in commandline.
Prabhuk Aug 26, 2025
51ef95e
Handle subcommand errors in parsing through lambdas.
Prabhuk Aug 27, 2025
043bbc8
Address review comments.
Prabhuk Sep 15, 2025
bc8bd1b
Fix initialization order of IgnoreCase.
Prabhuk Sep 15, 2025
dcb6779
Address review comments 2/
Prabhuk Sep 15, 2025
e7ea647
Address review comments 3/
Prabhuk Sep 15, 2025
ff8034a
Update inline comment.
Prabhuk Sep 15, 2025
e99a9a0
Address review comments 4/
Prabhuk Sep 15, 2025
a480c0c
Merge branch 'main' into opttable_subcommands_working
Prabhuk Sep 23, 2025
d41c2ae
Address review comments.
Prabhuk Sep 23, 2025
c1cddc9
Remove TopLevelCommand abstraction completely.
Prabhuk Sep 23, 2025
e28d2b0
Update variable names to reflect there is no Toplevelcommand.
Prabhuk Sep 23, 2025
3bc51b7
Update macro definition variable names
Prabhuk Sep 23, 2025
1feae67
More refactorings
Prabhuk Sep 23, 2025
66af1aa
Add subcommand unit tests.
Prabhuk Sep 25, 2025
b907a9c
More test cases.
Prabhuk Sep 25, 2025
9485199
Address review comments. Improve inline comments.
Prabhuk Sep 29, 2025
5c31cea
Fix formatting.
Prabhuk Sep 29, 2025
5995d18
Unfix unnecessary formatting.
Prabhuk Sep 29, 2025
064868a
Remove empty lines and braces on single line loop body.
Prabhuk Sep 29, 2025
c5a0cf5
Fix unittest to be more verbose.
Prabhuk Sep 29, 2025
dfc592e
Format.
Prabhuk Sep 29, 2025
8d11673
Fix the SubCommands find_if return type.
Prabhuk Sep 30, 2025
db25a6a
Introduce API to check if a subcommand is registered with a given opt…
Prabhuk Sep 30, 2025
ebdb5b8
Make API name consistent.
Prabhuk Sep 30, 2025
3d7c81e
Add test to handle unregistered subcommand option case.
Prabhuk Sep 30, 2025
845a073
Merge branch 'main' into opttable_subcommands_working
Prabhuk Oct 2, 2025
396a041
Address review comments.
Prabhuk Oct 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion clang-tools-extra/clangd/CompileCommands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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, SUBCOMMANDIDS_OFFSET) \
{DriverID::OPT_##ID, DriverID::OPT_##ALIAS, ALIASARGS},
#include "clang/Driver/Options.inc"
#undef OPTION
Expand Down
11 changes: 6 additions & 5 deletions clang/lib/Frontend/CompilerInvocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) \
Expand All @@ -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 || \
Expand Down
2 changes: 1 addition & 1 deletion clang/tools/clang-installapi/Options.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions lld/MachO/DriverUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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, \
Expand All @@ -59,7 +59,8 @@ static constexpr OptTable::Info optInfo[] = {
OPT_##GROUP, \
OPT_##ALIAS, \
ALIASARGS, \
VALUES},
VALUES, \
SUBCOMMANDIDS_OFFSET},
#include "Options.inc"
#undef OPTION
};
Expand Down
5 changes: 3 additions & 2 deletions lld/MinGW/Driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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, \
Expand All @@ -83,7 +83,8 @@ static constexpr opt::OptTable::Info infoTable[] = {
OPT_##GROUP, \
OPT_##ALIAS, \
ALIASARGS, \
VALUES},
VALUES, \
SUBCOMMANDIDS_OFFSET},
#include "Options.inc"
#undef OPTION
};
Expand Down
5 changes: 3 additions & 2 deletions lld/wasm/Driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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, \
Expand All @@ -171,7 +171,8 @@ static constexpr opt::OptTable::Info optInfo[] = {
OPT_##GROUP, \
OPT_##ALIAS, \
ALIASARGS, \
VALUES},
VALUES, \
SUBCOMMANDIDS_OFFSET},
#include "Options.inc"
#undef OPTION
};
Expand Down
1 change: 1 addition & 0 deletions llvm/examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
19 changes: 19 additions & 0 deletions llvm/examples/OptSubcommand/CMakeLists.txt
Copy link
Member

@petrhosek petrhosek Oct 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than having a directory in examples dedicated to just subcommands, I think we should a directory for Option and subcommands should be just one of the examples in there.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll start a new PR for that change.

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Set the .td file to be processed for this target.
set(LLVM_TARGET_DEFINITIONS Opts.td)

tablegen(LLVM Opts.inc -gen-opt-parser-defs)
add_public_tablegen_target(HelloSubTableGen)

set(LLVM_LINK_COMPONENTS
Support
Option
)

add_llvm_example(OptSubcommand
llvm-hello-sub.cpp
)

target_include_directories(OptSubcommand
PRIVATE
${CMAKE_CURRENT_BINARY_DIR}
)
18 changes: 18 additions & 0 deletions llvm/examples/OptSubcommand/Opts.td
Original file line number Diff line number Diff line change
@@ -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">;
124 changes: 124 additions & 0 deletions llvm/examples/OptSubcommand/llvm-hello-sub.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#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;
}
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 (Args.hasArg(OPT_uppercase))
llvm::outs() << "FOO\n";
else if (Args.hasArg(OPT_lowercase))
llvm::outs() << "foo\n";

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

if (Args.hasArg(OPT_version))
llvm::outs() << "LLVM Hello Subcommand bar Example 1.0\n";
}

return 0;
}
17 changes: 17 additions & 0 deletions llvm/include/llvm/Option/ArgList.h
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -280,6 +281,22 @@ class ArgList {
/// list.
virtual unsigned getNumInputArgStrings() const = 0;

/// getSubcommand - Find subcommand from the arguments if the usage is valid.
///
/// \param AllSubCommands - A list of all valid subcommands.
/// \param HandleMultipleSubcommands - A callback for the case where multiple
/// subcommands are present in the arguments. It gets a list of all found
/// subcommands.
/// \param HandleOtherPositionals - A callback for the case where positional
/// arguments that are not subcommands are present.
/// \return The name of the subcommand found. If no subcommand is found,
/// this returns an empty StringRef. If multiple subcommands are found, the
/// first one is returned.
StringRef getSubcommand(
ArrayRef<OptTable::SubCommand> AllSubCommands,
std::function<void(ArrayRef<StringRef>)> HandleMultipleSubcommands,
std::function<void(ArrayRef<StringRef>)> HandleOtherPositionals) const;
Comment on lines +297 to +298
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add comments above briefly explaining what these callbacks are for. Mostly requesting since the implication to me (based on the example usage) is that a user might want to just throw an error and exit. If this is the primary/only desirable case, then that might be worth noting. I personally can't think of a reason one might not want to print help and exit, but maybe someone else might have a reason to. We could probably discourage that with comments or maybe saying the callback is NoReturn if we want.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. PTAL.


/// @}
/// @name Argument Lookup Utilities
/// @{
Expand Down
46 changes: 31 additions & 15 deletions llvm/include/llvm/Option/OptParser.td
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.

Expand Down
Loading