Feature: Modernize argument parsing API

This commit is contained in:
Vicki Pfau 2022-04-21 18:42:48 -07:00
parent 770b121b10
commit 4556d4b121
12 changed files with 200 additions and 109 deletions

View File

@ -31,12 +31,20 @@ struct mArguments {
bool showVersion;
};
struct mOption {
const char* name;
bool arg;
char shortEquiv;
};
struct mCoreConfig;
struct mSubParser {
const char* usage;
bool (*parse)(struct mSubParser* parser, int option, const char* arg);
bool (*parseLong)(struct mSubParser* parser, const char* option, const char* arg);
void (*apply)(struct mSubParser* parser, struct mCoreConfig* config);
const char* extraOptions;
const struct mOption* longOptions;
void* opts;
};
@ -45,15 +53,14 @@ struct mGraphicsOpts {
bool fullscreen;
};
bool parseArguments(struct mArguments* args, int argc, char* const* argv,
struct mSubParser* subparser);
void applyArguments(const struct mArguments* args, struct mSubParser* subparser, struct mCoreConfig* config);
void freeArguments(struct mArguments* args);
void usage(const char* arg0, const char* extraOptions);
void usage(const char* arg0, const char* prologue, const char* epilogue, const struct mSubParser* subparsers, int nSubparsers);
void version(const char* arg0);
void initParserForGraphics(struct mSubParser* parser, struct mGraphicsOpts* opts);
bool mArgumentsParse(struct mArguments* args, int argc, char* const* argv, struct mSubParser* subparsers, int nSubparsers);
void mArgumentsApply(const struct mArguments* args, struct mSubParser* subparsers, int nSubparsers, struct mCoreConfig* config);
void mArgumentsDeinit(struct mArguments* args);
void mSubParserGraphicsInit(struct mSubParser* parser, struct mGraphicsOpts* opts);
CXX_GUARD_END

View File

@ -16,16 +16,12 @@
#include <getopt.h>
#endif
#define GRAPHICS_OPTIONS "123456f"
#define GRAPHICS_OPTIONS "12345678f"
#define GRAPHICS_USAGE \
"\nGraphics options:\n" \
" -1 1x viewport\n" \
" -2 2x viewport\n" \
" -3 3x viewport\n" \
" -4 4x viewport\n" \
" -5 5x viewport\n" \
" -6 6x viewport\n" \
" -f Start full-screen"
"Graphics options:\n" \
" -1, -2, -3, -4, -5, -6, -7, -8 Scale viewport by 1-8 times\n" \
" -f, --fullscreen Start full-screen\n" \
" --scale X Scale viewport by X times"
static const struct option _options[] = {
{ "bios", required_argument, 0, 'b' },
@ -45,7 +41,14 @@ static const struct option _options[] = {
{ 0, 0, 0, 0 }
};
static const struct mOption _graphicsLongOpts[] = {
{ "fullscreen", false, 'f' },
{ "scale", true, '\0' },
{ 0, 0, 0 }
};
static bool _parseGraphicsArg(struct mSubParser* parser, int option, const char* arg);
static bool _parseLongGraphicsArg(struct mSubParser* parser, const char* option, const char* arg);
static void _applyGraphicsArgs(struct mSubParser* parser, struct mCoreConfig* config);
static void _tableInsert(struct Table* table, const char* pair) {
@ -65,9 +68,9 @@ static void _tableApply(const char* key, void* value, void* user) {
mCoreConfigSetOverrideValue(config, key, value);
}
bool parseArguments(struct mArguments* args, int argc, char* const* argv, struct mSubParser* subparser) {
bool mArgumentsParse(struct mArguments* args, int argc, char* const* argv, struct mSubParser* subparsers, int nSubparsers) {
int ch;
char options[64] =
char options[128] =
"b:c:C:hl:p:s:t:"
#ifdef USE_EDITLINE
"d"
@ -76,23 +79,51 @@ bool parseArguments(struct mArguments* args, int argc, char* const* argv, struct
"g"
#endif
;
struct option longOptions[128] = {0};
memcpy(longOptions, _options, sizeof(_options));
memset(args, 0, sizeof(*args));
args->frameskip = -1;
args->logLevel = INT_MIN;
HashTableInit(&args->configOverrides, 0, free);
if (subparser && subparser->extraOptions) {
// TODO: modularize options to subparsers
strncat(options, subparser->extraOptions, sizeof(options) - strlen(options) - 1);
int lastLongOpt;
int i, j;
for (i = 0; _options[i].name; ++i); // Seek to end
lastLongOpt = i;
for (i = 0; i < nSubparsers; ++i) {
if (subparsers[i].extraOptions) {
strncat(options, subparsers[i].extraOptions, sizeof(options) - strlen(options) - 1);
}
if (subparsers[i].longOptions) {
for (j = 0; subparsers[i].longOptions[j].name; ++j) {
longOptions[lastLongOpt].name = subparsers[i].longOptions[j].name;
longOptions[lastLongOpt].has_arg = subparsers[i].longOptions[j].arg ? required_argument : no_argument;
longOptions[lastLongOpt].flag = NULL;
longOptions[lastLongOpt].val = subparsers[i].longOptions[j].shortEquiv;
++lastLongOpt;
}
}
}
bool ok = false;
int index = 0;
while ((ch = getopt_long(argc, argv, options, _options, &index)) != -1) {
const struct option* opt = &_options[index];
while ((ch = getopt_long(argc, argv, options, longOptions, &index)) != -1) {
const struct option* opt = &longOptions[index];
switch (ch) {
case '\0':
if (strcmp(opt->name, "version") == 0) {
args->showVersion = true;
} else {
return false;
for (i = 0; i < nSubparsers; ++i) {
if (subparsers[i].parseLong) {
ok = subparsers[i].parseLong(&subparsers[i], opt->name, optarg) || ok;
}
}
if (!ok) {
return false;
}
}
break;
case 'b':
@ -136,11 +167,14 @@ bool parseArguments(struct mArguments* args, int argc, char* const* argv, struct
args->savestate = strdup(optarg);
break;
default:
if (subparser) {
if (!subparser->parse(subparser, ch, optarg)) {
return false;
for (i = 0; i < nSubparsers; ++i) {
if (subparsers[i].parse) {
ok = subparsers[i].parse(&subparsers[i], ch, optarg) || ok;
}
}
if (!ok) {
return false;
}
break;
}
}
@ -156,7 +190,7 @@ bool parseArguments(struct mArguments* args, int argc, char* const* argv, struct
return true;
}
void applyArguments(const struct mArguments* args, struct mSubParser* subparser, struct mCoreConfig* config) {
void mArgumentsApply(const struct mArguments* args, struct mSubParser* subparsers, int nSubparsers, struct mCoreConfig* config) {
if (args->frameskip >= 0) {
mCoreConfigSetOverrideIntValue(config, "frameskip", args->frameskip);
}
@ -168,12 +202,15 @@ void applyArguments(const struct mArguments* args, struct mSubParser* subparser,
mCoreConfigSetOverrideIntValue(config, "useBios", true);
}
HashTableEnumerate(&args->configOverrides, _tableApply, config);
if (subparser) {
subparser->apply(subparser, config);
int i;
for (i = 0; i < nSubparsers; ++i) {
if (subparsers[i].apply) {
subparsers[i].apply(&subparsers[i], config);
}
}
}
void freeArguments(struct mArguments* args) {
void mArgumentsDeinit(struct mArguments* args) {
free(args->fname);
args->fname = 0;
@ -192,12 +229,14 @@ void freeArguments(struct mArguments* args) {
HashTableDeinit(&args->configOverrides);
}
void initParserForGraphics(struct mSubParser* parser, struct mGraphicsOpts* opts) {
void mSubParserGraphicsInit(struct mSubParser* parser, struct mGraphicsOpts* opts) {
parser->usage = GRAPHICS_USAGE;
parser->opts = opts;
parser->parse = _parseGraphicsArg;
parser->parseLong = _parseLongGraphicsArg;
parser->apply = _applyGraphicsArgs;
parser->extraOptions = GRAPHICS_OPTIONS;
parser->longOptions = _graphicsLongOpts;
opts->multiplier = 0;
opts->fullscreen = false;
}
@ -215,6 +254,8 @@ bool _parseGraphicsArg(struct mSubParser* parser, int option, const char* arg) {
case '4':
case '5':
case '6':
case '7':
case '8':
if (graphicsOpts->multiplier) {
return false;
}
@ -225,6 +266,18 @@ bool _parseGraphicsArg(struct mSubParser* parser, int option, const char* arg) {
}
}
bool _parseLongGraphicsArg(struct mSubParser* parser, const char* option, const char* arg) {
struct mGraphicsOpts* graphicsOpts = parser->opts;
if (strcmp(option, "scale") == 0) {
if (graphicsOpts->multiplier) {
return false;
}
graphicsOpts->multiplier = atoi(arg);
return graphicsOpts->multiplier != 0;
}
return false;
}
void _applyGraphicsArgs(struct mSubParser* parser, struct mCoreConfig* config) {
struct mGraphicsOpts* graphicsOpts = parser->opts;
if (graphicsOpts->fullscreen) {
@ -232,25 +285,36 @@ void _applyGraphicsArgs(struct mSubParser* parser, struct mCoreConfig* config) {
}
}
void usage(const char* arg0, const char* extraOptions) {
void usage(const char* arg0, const char* prologue, const char* epilogue, const struct mSubParser* subparsers, int nSubparsers) {
printf("usage: %s [option ...] file\n", arg0);
puts("\nGeneric options:");
puts(" -b, --bios FILE GBA BIOS file to use");
puts(" -c, --cheats FILE Apply cheat codes from a file");
puts(" -C, --config OPTION=VALUE Override config value");
if (prologue) {
puts(prologue);
}
puts("\nGeneric options:\n"
" -b, --bios FILE GBA BIOS file to use\n"
" -c, --cheats FILE Apply cheat codes from a file\n"
" -C, --config OPTION=VALUE Override config value\n"
#ifdef USE_EDITLINE
puts(" -d, --debug Use command-line debugger");
" -d, --debug Use command-line debugger\n"
#endif
#ifdef USE_GDB_STUB
puts(" -g, --gdb Start GDB session (default port 2345)");
" -g, --gdb Start GDB session (default port 2345)\n"
#endif
puts(" -l, --log-level N Log level mask");
puts(" -t, --savestate FILE Load savestate when starting");
puts(" -p, --patch FILE Apply a specified patch file when running");
puts(" -s, --frameskip N Skip every N frames");
puts(" --version Print version and exit");
if (extraOptions) {
puts(extraOptions);
" -l, --log-level N Log level mask\n"
" -t, --savestate FILE Load savestate when starting\n"
" -p, --patch FILE Apply a specified patch file when running\n"
" -s, --frameskip N Skip every N frames\n"
" --version Print version and exit"
);
int i;
for (i = 0; i < nSubparsers; ++i) {
if (subparsers[i].usage) {
puts("");
puts(subparsers[i].usage);
}
}
if (epilogue) {
puts(epilogue);
}
}

View File

@ -16,14 +16,14 @@ int main(int argc, char** argv) {
// Arguments from the command line are parsed by the parseArguments function.
// The NULL here shows that we don't give it any arguments beyond the default ones.
struct mArguments args = {};
bool parsed = parseArguments(&args, argc, argv, NULL);
bool parsed = mArgumentsParse(&args, argc, argv, NULL, 0);
// Parsing can succeed without finding a filename, but we need one.
if (!args.fname) {
parsed = false;
}
if (!parsed || args.showHelp) {
// If parsing failed, or the user passed --help, show usage.
usage(argv[0], NULL);
usage(argv[0], NULL, NULL, NULL, 0);
didFail = !parsed;
goto cleanup;
}
@ -65,7 +65,7 @@ int main(int argc, char** argv) {
SocketSubsystemDeinit();
cleanup:
freeArguments(&args);
mArgumentsDeinit(&args);
return didFail;
}
@ -114,7 +114,7 @@ bool _mExampleRun(const struct mArguments* args, Socket client) {
// loaded into the config system, as well as manually overriding the
// "idleOptimization" setting to ensure cores that can detect idle loops
// will attempt the detection.
applyArguments(args, NULL, &core->config);
mArgumentsApply(args, NULL, 0, &core->config);
mCoreConfigSetDefaultValue(&core->config, "idleOptimization", "detect");
// Tell the core to apply the configuration in the associated config object.

View File

@ -130,13 +130,22 @@ ConfigController::ConfigController(QObject* parent)
ConfigController::~ConfigController() {
mCoreConfigDeinit(&m_config);
mCoreConfigFreeOpts(&m_opts);
if (m_parsed) {
mArgumentsDeinit(&m_args);
}
}
bool ConfigController::parseArguments(mArguments* args, int argc, char* argv[], mSubParser* subparser) {
if (::parseArguments(args, argc, argv, subparser)) {
bool ConfigController::parseArguments(int argc, char* argv[]) {
if (m_parsed) {
return false;
}
mSubParserGraphicsInit(&m_subparsers[0], &m_graphicsOpts);
if (mArgumentsParse(&m_args, argc, argv, m_subparsers.data(), m_subparsers.size())) {
mCoreConfigFreeOpts(&m_opts);
applyArguments(args, subparser, &m_config);
mArgumentsApply(&m_args, m_subparsers.data(), m_subparsers.size(), &m_config);
mCoreConfigMap(&m_config, &m_opts);
m_parsed = true;
return true;
}
return false;
@ -299,6 +308,10 @@ void ConfigController::makePortable() {
m_settings = std::move(settings2);
}
void ConfigController::usage(const char* arg0) const {
::usage(arg0, nullptr, nullptr, m_subparsers.data(), m_subparsers.size());
}
bool ConfigController::isPortable() {
return mCoreConfigIsPortable();
}

View File

@ -12,6 +12,7 @@
#include <QSettings>
#include <QVariant>
#include <array>
#include <functional>
#include <memory>
@ -70,7 +71,7 @@ public:
~ConfigController();
const mCoreOptions* options() const { return &m_opts; }
bool parseArguments(mArguments* args, int argc, char* argv[], mSubParser* subparser = nullptr);
bool parseArguments(int argc, char* argv[]);
ConfigOption* addOption(const char* key);
void updateOption(const char* key);
@ -91,6 +92,10 @@ public:
const mCoreConfig* config() const { return &m_config; }
mCoreConfig* config() { return &m_config; }
const mArguments* args() const { return &m_args; }
const mGraphicsOpts* graphicsOpts() const { return &m_graphicsOpts; }
void usage(const char* arg0) const;
static const QString& configDir();
static bool isPortable();
@ -110,6 +115,10 @@ private:
mCoreConfig m_config;
mCoreOptions m_opts{};
mArguments m_args{};
mGraphicsOpts m_graphicsOpts{};
std::array<mSubParser, 1> m_subparsers;
bool m_parsed = false;
QHash<QString, ConfigOption*> m_optionSet;
std::unique_ptr<QSettings> m_settings;

View File

@ -179,8 +179,8 @@ Window::~Window() {
#endif
}
void Window::argumentsPassed(mArguments* args) {
loadConfig();
void Window::argumentsPassed() {
const mArguments* args = m_config->args();
if (args->patch) {
m_pendingPatch = args->patch;
@ -203,9 +203,24 @@ void Window::argumentsPassed(mArguments* args) {
}
#endif
if (m_config->graphicsOpts()->multiplier) {
m_savedScale = m_config->graphicsOpts()->multiplier;
#if defined(M_CORE_GBA)
QSize size(GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS);
#elif defined(M_CORE_GB)
QSize size(GB_VIDEO_HORIZONTAL_PIXELS, GB_VIDEO_VERTICAL_PIXELS);
#endif
resizeFrame(size * m_savedScale);
}
if (args->fname) {
setController(m_manager->loadGame(args->fname), args->fname);
}
if (m_config->graphicsOpts()->fullscreen) {
enterFullScreen();
}
}
void Window::resizeFrame(const QSize& size) {

View File

@ -24,8 +24,6 @@
#include "LogController.h"
#include "SettingsView.h"
struct mArguments;
namespace QGBA {
class AudioProcessor;
@ -59,7 +57,7 @@ public:
void setConfig(ConfigController*);
ConfigController* config() { return m_config; }
void argumentsPassed(mArguments*);
void argumentsPassed();
void resizeFrame(const QSize& size);

View File

@ -68,22 +68,18 @@ int main(int argc, char* argv[]) {
QLocale::setDefault(locale);
}
mArguments args;
mGraphicsOpts graphicsOpts;
mSubParser subparser;
initParserForGraphics(&subparser, &graphicsOpts);
bool loaded = configController.parseArguments(&args, argc, argv, &subparser);
if (loaded) {
if (args.showHelp) {
usage(argv[0], subparser.usage);
freeArguments(&args);
if (configController.parseArguments(argc, argv)) {
if (configController.args()->showHelp) {
configController.usage(argv[0]);
return 0;
}
if (args.showVersion) {
if (configController.args()->showVersion) {
version(argv[0]);
freeArguments(&args);
return 0;
}
} else {
configController.usage(argv[0]);
return 1;
}
QApplication::setApplicationName(projectName);
@ -117,19 +113,8 @@ int main(int argc, char* argv[]) {
application.installTranslator(&langTranslator);
Window* w = application.newWindow();
if (loaded) {
w->argumentsPassed(&args);
} else {
w->loadConfig();
}
freeArguments(&args);
if (graphicsOpts.multiplier) {
w->resizeFrame(QSize(GBA_VIDEO_HORIZONTAL_PIXELS * graphicsOpts.multiplier, GBA_VIDEO_VERTICAL_PIXELS * graphicsOpts.multiplier));
}
if (graphicsOpts.fullscreen) {
w->enterFullScreen();
}
w->loadConfig();
w->argumentsPassed();
w->show();

View File

@ -80,37 +80,37 @@ int main(int argc, char** argv) {
struct mSubParser subparser;
initParserForGraphics(&subparser, &graphicsOpts);
bool parsed = parseArguments(&args, argc, argv, &subparser);
mSubParserGraphicsInit(&subparser, &graphicsOpts);
bool parsed = mArgumentsParse(&args, argc, argv, &subparser, 1);
if (!args.fname && !args.showVersion) {
parsed = false;
}
if (!parsed || args.showHelp) {
usage(argv[0], subparser.usage);
freeArguments(&args);
usage(argv[0], NULL, NULL, &subparser, 1);
mArgumentsDeinit(&args);
return !parsed;
}
if (args.showVersion) {
version(argv[0]);
freeArguments(&args);
mArgumentsDeinit(&args);
return 0;
}
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
printf("Could not initialize video: %s\n", SDL_GetError());
freeArguments(&args);
mArgumentsDeinit(&args);
return 1;
}
renderer.core = mCoreFind(args.fname);
if (!renderer.core) {
printf("Could not run game. Are you sure the file exists and is a compatible game?\n");
freeArguments(&args);
mArgumentsDeinit(&args);
return 1;
}
if (!renderer.core->init(renderer.core)) {
freeArguments(&args);
mArgumentsDeinit(&args);
return 1;
}
@ -134,7 +134,7 @@ int main(int argc, char** argv) {
mInputMapInit(&renderer.core->inputMap, &GBAInputInfo);
mCoreInitConfig(renderer.core, PORT);
applyArguments(&args, &subparser, &renderer.core->config);
mArgumentsApply(&args, &subparser, 1, &renderer.core->config);
mCoreConfigLoadDefaults(&renderer.core->config, &opts);
mCoreLoadConfig(renderer.core);
@ -168,7 +168,7 @@ int main(int argc, char** argv) {
}
if (!renderer.init(&renderer)) {
freeArguments(&args);
mArgumentsDeinit(&args);
mCoreConfigDeinit(&renderer.core->config);
renderer.core->deinit(renderer.core);
return 1;
@ -199,7 +199,7 @@ int main(int argc, char** argv) {
mSDLDeinit(&renderer);
freeArguments(&args);
mArgumentsDeinit(&args);
mCoreConfigFreeOpts(&opts);
mCoreConfigDeinit(&renderer.core->config);
renderer.core->deinit(renderer.core);

View File

@ -22,7 +22,7 @@
#define FUZZ_OPTIONS "F:NO:S:V:"
#define FUZZ_USAGE \
"\nAdditional options:\n" \
"Additional options:\n" \
" -F FRAMES Run for the specified number of FRAMES before exiting\n" \
" -N Disable video rendering entirely\n" \
" -O OFFSET Offset to apply savestate overlay\n" \
@ -53,12 +53,12 @@ int main(int argc, char** argv) {
};
struct mArguments args;
bool parsed = parseArguments(&args, argc, argv, &subparser);
bool parsed = mArgumentsParse(&args, argc, argv, &subparser, 1);
if (!args.fname) {
parsed = false;
}
if (!parsed || args.showHelp) {
usage(argv[0], FUZZ_USAGE);
usage(argv[0], NULL, NULL, &subparser, 1);
return !parsed;
}
if (args.showVersion) {
@ -71,7 +71,7 @@ int main(int argc, char** argv) {
}
core->init(core);
mCoreInitConfig(core, "fuzz");
applyArguments(&args, NULL, &core->config);
mArgumentsApply(&args, NULL, 0, &core->config);
mCoreConfigSetDefaultValue(&core->config, "idleOptimization", "remove");
@ -161,7 +161,7 @@ int main(int argc, char** argv) {
}
loadError:
freeArguments(&args);
mArgumentsDeinit(&args);
if (outputBuffer) {
free(outputBuffer);
}

View File

@ -44,7 +44,7 @@ size_t romBufferSize;
#define PERF_OPTIONS "DF:L:NPS:T"
#define PERF_USAGE \
"\nBenchmark options:\n" \
"Benchmark options:\n" \
" -F FRAMES Run for the specified number of FRAMES before exiting\n" \
" -N Disable video rendering entirely\n" \
" -T Use threaded video rendering\n" \
@ -131,12 +131,12 @@ int main(int argc, char** argv) {
};
struct mArguments args = {};
bool parsed = parseArguments(&args, argc, argv, &subparser);
bool parsed = mArgumentsParse(&args, argc, argv, &subparser, 1);
if (!args.fname && !perfOpts.server) {
parsed = false;
}
if (!parsed || args.showHelp) {
usage(argv[0], PERF_USAGE);
usage(argv[0], NULL, NULL, &subparser, 1);
didFail = !parsed;
goto cleanup;
}
@ -171,7 +171,7 @@ int main(int argc, char** argv) {
_savestate->close(_savestate);
}
cleanup:
freeArguments(&args);
mArgumentsDeinit(&args);
#ifdef __3DS__
gfxExit();
@ -217,7 +217,7 @@ bool _mPerfRunCore(const char* fname, const struct mArguments* args, const struc
mCoreConfigMap(&core->config, &opts);
opts.audioSync = false;
opts.videoSync = false;
applyArguments(args, NULL, &core->config);
mArgumentsApply(args, NULL, 0, &core->config);
mCoreConfigLoadDefaults(&core->config, &opts);
mCoreConfigSetDefaultValue(&core->config, "idleOptimization", "detect");
mCoreLoadConfig(core);

View File

@ -24,7 +24,7 @@
#define ROM_TEST_OPTIONS "S:R:"
#define ROM_TEST_USAGE \
"\nAdditional options:\n" \
"Additional options:\n" \
" -S SWI Run until specified SWI call before exiting\n" \
" -R REGISTER General purpose register to return as exit code\n" \
@ -81,12 +81,12 @@ int main(int argc, char * argv[]) {
};
struct mArguments args;
bool parsed = parseArguments(&args, argc, argv, &subparser);
bool parsed = mArgumentsParse(&args, argc, argv, &subparser, 1);
if (!args.fname) {
parsed = false;
}
if (!parsed || args.showHelp) {
usage(argv[0], ROM_TEST_USAGE);
usage(argv[0], NULL, NULL, &subparser, 1);
return !parsed;
}
if (args.showVersion) {
@ -99,7 +99,7 @@ int main(int argc, char * argv[]) {
}
core->init(core);
mCoreInitConfig(core, "romTest");
applyArguments(&args, NULL, &core->config);
mArgumentsApply(&args, NULL, 0, &core->config);
mCoreConfigSetDefaultValue(&core->config, "idleOptimization", "remove");
@ -184,7 +184,7 @@ int main(int argc, char * argv[]) {
cleanExit = true;
loadError:
freeArguments(&args);
mArgumentsDeinit(&args);
mCoreConfigDeinit(&core->config);
core->deinit(core);