diff --git a/include/libtrx/arg_parser.h b/include/libtrx/arg_parser.h new file mode 100644 index 0000000..aacdfc8 --- /dev/null +++ b/include/libtrx/arg_parser.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include + +typedef enum { + ARG_PARSER_TYPE_BOOL, + ARG_PARSER_TYPE_STRING, + ARG_PARSER_TYPE_INT, + ARG_PARSER_TYPE_FLOAT, +} ARG_PARSER_TYPE; + +typedef struct { + char *id; + bool is_named; + char *short_name; // for named arguments like -v + char *long_name; // for named arguments like --verbose + ARG_PARSER_TYPE type; + bool mandatory; + struct { + bool bool_val; + char *string_val; + int32_t int_val; + float float_val; + } value; + bool provided; +} ARG_PARSER_ARGUMENT; + +typedef struct { + ARG_PARSER_ARGUMENT *args; + int32_t argc; + int32_t capacity; +} ARG_PARSER; + +void ArgParser_Init(ARG_PARSER *const parser); +void ArgParser_Destroy(ARG_PARSER *const parser); + +void ArgParser_AddArgument( + ARG_PARSER *const parser, const ARG_PARSER_ARGUMENT *user_arg); + +const ARG_PARSER_ARGUMENT *ArgParser_GetArgument( + const ARG_PARSER *const parser, const char *id); + +bool ArgParser_ParseCmdline(ARG_PARSER *parser, const char *cmdline); +bool ArgParser_ParseArgs(ARG_PARSER *parser, int32_t argc, const char **argv); diff --git a/meson.build b/meson.build index 3bfa0c8..486e563 100644 --- a/meson.build +++ b/meson.build @@ -45,6 +45,7 @@ if not staticdeps endif sources = [ + 'src/arg_parser.c', 'src/benchmark.c', 'src/config/config_file.c', 'src/engine/audio.c', diff --git a/src/arg_parser.c b/src/arg_parser.c new file mode 100644 index 0000000..68b0733 --- /dev/null +++ b/src/arg_parser.c @@ -0,0 +1,282 @@ +#include "arg_parser.h" + +#include "memory.h" + +#include +#include +#include +#include + +#define TOKEN_BUFSIZE 64 +#define TOKEN_DELIM " \t\r\n\a" + +static void ArgParser_FillValue(ARG_PARSER_ARGUMENT *target, const char *value); +static bool ArgParser_ValueMatches( + ARG_PARSER_ARGUMENT *target, const char *value); +static char **ArgParser_ShellSplit(const char *cmdline, int *argc); + +static char **ArgParser_ShellSplit(const char *cmdline, int *const argc) +{ + int32_t bufsize = TOKEN_BUFSIZE; + int32_t position = 0; + char **tokens = Memory_Alloc(bufsize * sizeof(char *)); + char *token; + const char *str_i; + char *token_ptr; + int32_t token_length; + + while (*cmdline) { + while (isspace(*cmdline)) { + cmdline++; + } + + if (*cmdline == '\0') { + break; + } + + if (*cmdline == '"' || *cmdline == '\'') { + char quote = *cmdline++; + str_i = cmdline; + token_ptr = token = Memory_Alloc(bufsize); + while (*cmdline && *cmdline != quote) { + if (*cmdline == '\\' + && (*(cmdline + 1) == quote || *(cmdline + 1) == '\\')) { + cmdline++; // Skip escape character + *token_ptr++ = *cmdline++; + } else { + *token_ptr++ = *cmdline++; + } + + if (token_ptr - token >= bufsize) { + bufsize += TOKEN_BUFSIZE; + token = Memory_Realloc(token, bufsize); + token_ptr = token + (token_ptr - token); + } + } + *token_ptr = '\0'; + if (*cmdline) { + cmdline++; + } + } else { + str_i = cmdline; + token_ptr = token = Memory_Alloc(bufsize); + while (*cmdline && !isspace(*cmdline) && *cmdline != '"' + && *cmdline != '\'') { + if (*cmdline == '\\' + && (*(cmdline + 1) == '"' || *(cmdline + 1) == '\'' + || *(cmdline + 1) == '\\' || isspace(*(cmdline + 1)))) { + cmdline++; // Skip escape character + *token_ptr++ = *cmdline++; + } else { + *token_ptr++ = *cmdline++; + } + if (token_ptr - token >= bufsize) { + bufsize += TOKEN_BUFSIZE; + token = Memory_Realloc(token, bufsize); + token_ptr = token + (token_ptr - token); + } + } + *token_ptr = '\0'; + } + + tokens[position++] = token; + if (position >= bufsize) { + bufsize += TOKEN_BUFSIZE; + tokens = Memory_Realloc(tokens, bufsize * sizeof(char *)); + } + } + tokens[position] = NULL; + *argc = position; + return tokens; +} + +static void ArgParser_FillValue( + ARG_PARSER_ARGUMENT *const target, const char *const value) +{ + switch (target->type) { + case ARG_PARSER_TYPE_BOOL: + target->value.bool_val = + (strcmp(value, "1") == 0 || strcmp(value, "on") == 0); + break; + case ARG_PARSER_TYPE_STRING: + target->value.string_val = Memory_DupStr(value); + break; + case ARG_PARSER_TYPE_INT: + target->value.int_val = atoi(value); + break; + case ARG_PARSER_TYPE_FLOAT: + target->value.float_val = atof(value); + break; + } +} + +static bool ArgParser_ValueMatches( + ARG_PARSER_ARGUMENT *target, const char *value) +{ + // Checks whether a value can be coerced into a type. + // This is to make sure optional positional arguments do not try to + // populate numbers with strings. + + switch (target->type) { + case ARG_PARSER_TYPE_BOOL: + return (strcmp(value, "1") == 0 || strcmp(value, "on") == 0) + || (strcmp(value, "0") == 0 || strcmp(value, "off") == 0); + + case ARG_PARSER_TYPE_INT: + for (size_t i = 0; i < strlen(value); i++) { + if (!isdigit(value[i])) { + return false; + } + } + return true; + + case ARG_PARSER_TYPE_FLOAT: { + bool has_dot = false; + for (size_t i = 0; i < strlen(value); i++) { + if (!isdigit(value[i])) { + if (value[i] == '.') { + if (has_dot) { + return false; + } + has_dot = true; + } else { + return false; + } + } + } + return true; + } + + case ARG_PARSER_TYPE_STRING: + return true; + } + + return false; +} + +void ArgParser_Init(ARG_PARSER *const parser) +{ + parser->capacity = 10; + parser->argc = 0; + parser->args = Memory_Alloc(sizeof(ARG_PARSER_ARGUMENT) * parser->capacity); +} + +void ArgParser_Destroy(ARG_PARSER *const parser) +{ + for (int32_t i = 0; i < parser->argc; i++) { + ARG_PARSER_ARGUMENT *const arg = &parser->args[i]; + Memory_FreePointer(&arg->value.string_val); + Memory_FreePointer(&arg->id); + Memory_FreePointer(&arg->short_name); + Memory_FreePointer(&arg->long_name); + } + Memory_FreePointer(&parser->args); +} + +void ArgParser_AddArgument( + ARG_PARSER *const parser, const ARG_PARSER_ARGUMENT *user_arg) +{ + if (parser->argc == parser->capacity) { + parser->capacity *= 2; + parser->args = Memory_Realloc( + parser->args, sizeof(ARG_PARSER_ARGUMENT) * parser->capacity); + } + + ARG_PARSER_ARGUMENT *const arg = &parser->args[parser->argc++]; + arg->id = Memory_DupStr(user_arg->id); + arg->is_named = user_arg->is_named; + arg->short_name = + user_arg->short_name ? Memory_DupStr(user_arg->short_name) : NULL; + arg->long_name = + user_arg->long_name ? Memory_DupStr(user_arg->long_name) : NULL; + arg->type = user_arg->type; + arg->mandatory = user_arg->mandatory; + + arg->provided = false; + arg->value.bool_val = false; + arg->value.string_val = NULL; + arg->value.int_val = 0; + arg->value.float_val = 0.0f; +} + +const ARG_PARSER_ARGUMENT *ArgParser_GetArgument( + const ARG_PARSER *const parser, const char *id) +{ + for (int32_t i = 0; i < parser->argc; i++) { + ARG_PARSER_ARGUMENT *arg = &parser->args[i]; + if (strcmp(arg->id, id) == 0 && arg->provided) { + return arg; + } + } + return NULL; +} + +bool ArgParser_ParseCmdline(ARG_PARSER *const parser, const char *const cmdline) +{ + int32_t argc; + char **argv = ArgParser_ShellSplit(cmdline, &argc); + const bool result = ArgParser_ParseArgs(parser, argc, (const char **)argv); + for (int32_t i = 0; i < argc; i++) { + Memory_FreePointer(&argv[i]); + } + Memory_FreePointer(&argv); + return result; +} + +bool ArgParser_ParseArgs( + ARG_PARSER *const parser, const int32_t argc, const char **const argv) +{ + for (int32_t i = 0; i < argc; i++) { + bool matched = false; + + for (int32_t j = 0; j < parser->argc; j++) { + ARG_PARSER_ARGUMENT *arg = &parser->args[j]; + if (!arg->is_named) { + continue; + } + + if ((arg->short_name && strcmp(argv[i], arg->short_name) == 0) + || (arg->long_name && strcmp(argv[i], arg->long_name) == 0)) { + matched = true; + if (arg->type == ARG_PARSER_TYPE_BOOL) { + arg->value.bool_val = true; + } else { + if (++i >= argc) { + return false; + } + if (!ArgParser_ValueMatches(arg, argv[i])) { + return false; + } + ArgParser_FillValue(arg, argv[i]); + } + arg->provided = true; + break; + } + } + + if (!matched) { + for (int32_t j = 0; j < parser->argc; j++) { + ARG_PARSER_ARGUMENT *arg = &parser->args[j]; + if (!arg->is_named && !arg->provided + && ArgParser_ValueMatches(arg, argv[i])) { + matched = true; + ArgParser_FillValue(arg, argv[i]); + arg->provided = true; + break; + } + } + + if (!matched) { + return false; + } + } + } + + for (int32_t i = 0; i < parser->argc; i++) { + if (parser->args[i].mandatory && !parser->args[i].provided) { + return false; + } + } + + return true; +}