This repository has been archived by the owner on Oct 4, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
328 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
#pragma once | ||
|
||
#include <stdbool.h> | ||
#include <stdint.h> | ||
|
||
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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,282 @@ | ||
#include "arg_parser.h" | ||
|
||
#include "memory.h" | ||
|
||
#include <ctype.h> | ||
#include <stdio.h> | ||
#include <stdlib.h> | ||
#include <string.h> | ||
|
||
#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; | ||
} |