From d5a1db838b450bbd1c6997c5cf117a93b078e30c Mon Sep 17 00:00:00 2001 From: superp00t Date: Mon, 14 Aug 2023 16:25:33 -0400 Subject: [PATCH] feat(command): implement command-line argument parsing --- storm/Command.cpp | 512 ++++++++++++++++++++++++++++++++++++++++++++++ storm/Command.hpp | 125 +++++++++++ 2 files changed, 637 insertions(+) create mode 100644 storm/Command.cpp create mode 100644 storm/Command.hpp diff --git a/storm/Command.cpp b/storm/Command.cpp new file mode 100644 index 0000000..40740c4 --- /dev/null +++ b/storm/Command.cpp @@ -0,0 +1,512 @@ +#include "storm/Command.hpp" +#include "storm/Error.hpp" +#include "storm/String.hpp" + +#include "bc/os/File.hpp" + +#include +#include + +STORM_LIST(CMDDEF) s_arglist; +STORM_LIST(CMDDEF) s_flaglist; + +const char* s_errorstr[] = { + "Invalid argument: %s", + "The syntax of the command is incorrect.", + "Unable to open response file: %s" +}; + +static void GenerateError(CMDERRORCALLBACKFCN errorcallback, uint32_t errorcode, const char* itemstring) { + char errorstr[256] = {0}; + uint32_t strid = 0; + + switch (errorcode) { + case STORM_COMMAND_ERROR_BAD_ARGUMENT: + strid = 0; + break; + case STORM_COMMAND_ERROR_NOT_ENOUGH_ARGUMENTS: + strid = 1; + break; + case STORM_COMMAND_ERROR_OPEN_FAILED: + strid = 2; + break; + default: + return; + } + + auto source = s_errorstr[strid]; + STORM_ASSERT(source); + + char buffer[256] = {0}; + SStrCopy(buffer, s_errorstr[strid], 256); + + if (strstr(buffer, "%s")) { + SStrPrintf(buffer, 256, itemstring); + } else { + SStrCopy(errorstr, buffer, 256); + + if (errorstr[0]) { + SStrPack(errorstr, "\n", 256); + } + } + + SErrSetLastError(errorcode); + + CMDERROR data; + data.errorcode = errorcode; + data.itemstr = itemstring; + data.errorstr = errorstr; + errorcallback(&data); +} + +static CMDDEF* FindFlagDef(const char* string, CMDDEF* firstdef, int32_t minlength) { + CMDDEF* bestptr = nullptr; + int32_t match; + auto strlength = SStrLen(string); + auto bestlength = minlength - 1; + + for (auto def = firstdef; def; def = def->Next()) { + if ((def->namelength > bestlength) && (def->namelength <= strlength)) { + if (def->flags & STORM_COMMAND_EXTRA_CASE_SENSITIVE) { + match = !SStrCmp(def->name, string, def->namelength); + } else { + match = !SStrCmpI(def->name, string, def->namelength); + } + + if (match) { + bestlength = def->namelength; + bestptr = def; + } + } + } + + return bestptr; +} + + +static void ConvertBool(CMDDEF* ptr, const char* string, int32_t* datachars) { + int32_t set; + + if (*string == '-') { + set = 0; + *datachars = 1; + } else if (*string == '+') { + set = 1; + *datachars = 1; + } else if (STORM_COMMAND_GET_BOOL(ptr->flags) == STORM_COMMAND_BOOL_CLEAR) { + set = 0; + } else { + set = 1; + } + + auto notmask = ~ptr->setmask; + + ptr->currvalue &= notmask; + if (set) { + ptr->currvalue |= ptr->setvalue; + } + + if (ptr->variableptr) { + *(reinterpret_cast(ptr->variableptr)) &= notmask; + + if (set) { + *(reinterpret_cast(ptr->variableptr)) &= ptr->setvalue; + } + } +} + +static void ConvertNumber(CMDDEF* ptr, const char* string, int32_t* datachars) { + char* endptr = nullptr; + + if (STORM_COMMAND_GET_NUM(ptr->flags) == STORM_COMMAND_NUM_SIGNED) { + ptr->currvalue = static_cast(strtol(string, &endptr, 0)); + } else { + ptr->currvalue = static_cast(strtoul(string, &endptr, 0)); + } + + if (endptr) { + *datachars = endptr - string; + } else { + *datachars = SStrLen(string); + } + + if (ptr->variableptr) { + memcpy(ptr->variableptr, &ptr->currvaluestr, std::min(sizeof(ptr->currvalue), ptr->variablebytes)); + } +} + +static void ConvertString(CMDDEF* ptr, const char* string, int32_t* datachars) { + *datachars = SStrLen(string); + + if (ptr->currvaluestr) { + SMemFree(ptr->currvaluestr, __FILE__, __LINE__, 0); + } + + auto size = SStrLen(string) + 1; + auto ch = reinterpret_cast(SMemAlloc(size, __FILE__, __LINE__, 0)); + + SStrCopy(ch, string, size); + + ptr->currvaluestr = ch; + + if (ptr->variableptr) { + SStrCopy(reinterpret_cast(ptr->variableptr), string, ptr->variablebytes); + } +} + +static int32_t PerformConversion(CMDDEF* ptr, const char* string, int32_t* datachars) { + CMDPARAMS params; + + *datachars = 0; + + switch (STORM_COMMAND_GET_TYPE(ptr->flags)) { + case STORM_COMMAND_TYPE_BOOL: + ConvertBool(ptr, string, datachars); + break; + case STORM_COMMAND_TYPE_NUMBER: + ConvertNumber(ptr, string, datachars); + break; + case STORM_COMMAND_TYPE_STRING: + ConvertString(ptr, string, datachars); + break; + default: + return 0; + } + + ptr->found = 1; + + if (ptr->callback) { + params.flags = ptr->flags; + params.id = ptr->id; + params.name = ptr->name; + params.variable = ptr->variableptr; + params.setvalue = ptr->setvalue; + params.setmask = ptr->setmask; + params.unsignedvalue = ptr->currvalue; + + if (!ptr->callback(¶ms, string)) { + return 0; + } + } + + for (int32_t flaglist = 0; flaglist < 2; flaglist++) { + auto& list = flaglist ? s_flaglist : s_arglist; + + for (auto def = list.Head(); def; def = def->Next()) { + if ((def->id == ptr->id) && (STORM_COMMAND_GET_TYPE(def->flags) == STORM_COMMAND_GET_TYPE(ptr->flags)) && (def != ptr)) { + def->found = 1; + + if (STORM_COMMAND_GET_TYPE(def->flags) == STORM_COMMAND_TYPE_STRING) { + if (def->currvaluestr) { + SMemFree(def->currvaluestr, __FILE__, __LINE__, 0); + } + + auto newlen = SStrLen(ptr->currvaluestr) + 1; + def->currvaluestr = reinterpret_cast(SMemAlloc(newlen, __FILE__, __LINE__, 0)); + SStrCopy(def->currvaluestr, ptr->currvaluestr, newlen); + } else { + def->currvalue = ptr->currvalue; + } + } + } + } + + return 1; +} + +static int32_t ProcessCurrentFlag(const char* string, PROCESSING* processing, int32_t* datachars) { + *datachars = 0; + + auto ptr = processing->ptr; + processing->ptr = nullptr; + + int32_t currdatachars; + + while (ptr) { + if (!PerformConversion(ptr, string, &currdatachars)) { + return 0; + } + + *datachars = std::max(*datachars, currdatachars); + + ptr = FindFlagDef(processing->name, ptr->Next(), processing->namelength); + } + + return 1; +} + +static int32_t ProcessFlags(const char* string, PROCESSING* processing, CMDERRORCALLBACKFCN errorcallback) { + char lastflag[256] = { 0 }; + + while (string[0] != '\0') { + int32_t datachars; + CMDDEF* ptr = nullptr; + auto strlength = SStrLen(string); + auto lastflaglength = std::max(SStrLen(lastflag), 1); + + while (lastflaglength--) { + if (strlength + lastflaglength < 256) { + SStrCopy(&lastflag[lastflaglength], string, 256); + + ptr = FindFlagDef(lastflag, s_flaglist.Head(), 0); + + if (ptr) { + lastflaglength = ptr->namelength; + lastflag[lastflaglength] = '\0'; + break; + } + } + } + + if (!ptr) { + if (errorcallback) { + GenerateError(errorcallback, STORM_COMMAND_ERROR_BAD_ARGUMENT, string); + } + + return 0; + } + + string += lastflaglength; + + processing->ptr = ptr; + processing->namelength = lastflaglength; + SStrCopy(processing->name, lastflag, 16); + + if (!string[0] && STORM_COMMAND_GET_TYPE(ptr->flags) != STORM_COMMAND_TYPE_BOOLEAN) { + return 1; + } + + if (!ProcessCurrentFlag(string, processing, &datachars)) { + return 0; + } + + string += datachars; + } + + return 1; +} + +static int32_t ProcessToken(const char* string, int32_t quoted, PROCESSING* processing, CMDDEF** nextarg, CMDEXTRACALLBACKFCN extracallback, CMDERRORCALLBACKFCN errorcallback) { + if (string[0] == '@' && !quoted) { + return ProcessFile(&string[1], processing, nextarg, extracallback, errorcallback); + } + + int32_t datachars; + + if (SStrChr("-/", string[0]) != nullptr && !quoted) { + processing->ptr = nullptr; + return ProcessFlags(string + 1, processing, errorcallback); + } + + if (processing->ptr != nullptr) { + return ProcessCurrentFlag(string, processing, &datachars); + } + + if (*nextarg) { + if (!PerformConversion(*nextarg, string, &datachars)) { + return 0; + } + *nextarg = (*nextarg)->Next(); + return 1; + } else { + if (extracallback) { + return extracallback(string); + } + + if (errorcallback) { + GenerateError(errorcallback, STORM_COMMAND_ERROR_OPEN_FAILED, string); + } + } + + return 0; +} + +static int32_t ProcessString(const char** stringptr, PROCESSING* processing, CMDDEF** nextarg, CMDEXTRACALLBACKFCN extracallback, CMDERRORCALLBACKFCN errorcallback) { + char buffer[256] = {0}; + int32_t quoted = 0; + + while (**stringptr != '\0') { + auto nextptr = *stringptr; + + SStrTokenize(&nextptr, buffer, sizeof(buffer), STORM_COMMAND_WHITESPACE_CHARS, "ed); + + if (!ProcessToken(buffer, quoted, processing, nextarg, extracallback, errorcallback)) { + break; + } + + *stringptr = nextptr; + } + + return **stringptr == '\0'; +} + +static int32_t ProcessFile(const char* filename, PROCESSING* processing, CMDDEF** nextarg, CMDEXTRACALLBACKFCN extracallback, CMDERRORCALLBACKFCN errorcallback) { + // TODO + auto file = OsCreateFile(filename, OS_GENERIC_READ, OS_FILE_SHARE_READ, OS_OPEN_EXISTING, OS_FILE_FLAG_SEQUENTIAL_SCAN, nullptr); + + if (!file) { + if (errorcallback) { + GenerateError(errorcallback, STORM_COMMAND_ERROR_OPEN_FAILED, filename); + } + return false; + } + + auto size = OsGetFileSize(file); + + auto buffer = reinterpret_cast(SMemAlloc(size + 1, __FILE__, __LINE__, 0)); + + uint32_t bytesread = 0; + OsReadFile(file, buffer, size, &bytesread, nullptr); + + OsCloseFile(file); + + buffer[bytesread] = '\0'; + + auto curr = buffer; + auto status = ProcessString(&curr, processing, nextarg, extracallback, errorcallback); + + SMemFree(buffer, __FILE__, __LINE__); + + return status; +} + +int32_t SCmdRegisterArgument(uint32_t flags, uint32_t id, const char* name, void* variableptr, uint32_t variablebytes, uint32_t setvalue, uint32_t setmask, CMDPARAMSCALLBACKFCN callback) { + if (name == nullptr) { + name = ""; + } + + auto namelength = SStrLen(name); + + STORM_VALIDATE(namelength < 16, ERROR_INVALID_PARAMETER, 0); + STORM_VALIDATE((!variablebytes) || variableptr, ERROR_INVALID_PARAMETER, 0); + STORM_VALIDATE(((STORM_COMMAND_GET_ARG(flags) != STORM_COMMAND_ARG_REQUIRED) || !s_addedoptional), ERROR_INVALID_PARAMETER, 0); + STORM_VALIDATE((STORM_COMMAND_GET_ARG(flags) != STORM_COMMAND_TYPE_FLAGGED) || (namelength > 0), ERROR_INVALID_PARAMETER, 0); + STORM_VALIDATE((STORM_COMMAND_GET_TYPE(flags) != STORM_COMMAND_TYPE_BOOL) || (!variableptr) || (variablebytes == sizeof(uint32_t)), ERROR_INVALID_PARAMETER, 0); + + // If argument is flagged, it goes in the flag list + auto& cmdlist = s_arglist; + if (STORM_COMMAND_GET_ARG(flags) == STORM_COMMAND_ARG_FLAGGED) { + cmdlist = s_flaglist; + } + + auto cmd = cmdlist.NewNode(2, 0, 0); + + SStrCopy(cmd->name, name, sizeof(cmd->name)); + + cmd->id = id; + cmd->namelength = namelength; + cmd->variableptr = variableptr; + cmd->variablebytes = variablebytes; + cmd->flags = flags; + cmd->setvalue = setvalue; + cmd->setmask = setmask; + cmd->callback = callback; + + if ((STORM_COMMAND_GET_TYPE(flags) == STORM_COMMAND_TYPE_BOOL) && (STORM_COMMAND_GET_BOOL(flags) == STORM_COMMAND_BOOL_CLEAR)) { + cmd->currvalue = setvalue; + } else { + cmd->currvalue = 0; + } + + if (STORM_COMMAND_GET_ARG(flags) == STORM_COMMAND_ARG_OPTIONAL) { + s_addedoptional = 1; + } + + return 1; +} + +int32_t SCmdRegisterArgList(ARGLIST* listptr, uint32_t numargs) { + STORM_VALIDATE(listptr, ERROR_INVALID_PARAMETER, 0); + + for (int32_t i = 0; i < numargs; i++) { + if (!SCmdRegisterArgument(listptr->flags, listptr->id, listptr->name, 0, 0, 1, 0xFFFFFFFF, listptr->callback)) { + return 1; + } + + listptr++; + } + + return 1; +} + +int32_t SCmdProcess(const char* cmdline, int32_t skipprogname, CMDEXTRACALLBACKFCN extracallback, CMDERRORCALLBACKFCN errorcallback) { + STORM_VALIDATE(cmdline, ERROR_INVALID_PARAMETER, 0); + + if (skipprogname) { + SStrTokenize(&cmdline, nullptr, 0, STORM_COMMAND_WHITESPACE_CHARS, nullptr); + } + + PROCESSING processing; + memset(&processing, 0, sizeof(processing)); + + auto nextarg = s_arglist.Head(); + + if (!ProcessString(&cmdline, &processing, &nextarg, extracallback, errorcallback)) { + return 0; + } + + int32_t allfilled = 1; + + while (nextarg && allfilled) { + if (STORM_COMMAND_GET_ARG(nextarg->flags) == STORM_COMMAND_ARG_REQUIRED) { + allfilled = 0; + } else { + nextarg = nextarg->Next(); + } + } + + if (errorcallback && !allfilled) { + GenerateError(errorcallback, STORM_COMMAND_ERROR_NOT_ENOUGH_ARGUMENTS, ""); + } + + return 1; +} + +int32_t SCmdProcessCommandLine(CMDEXTRACALLBACKFCN extracallback, CMDERRORCALLBACKFCN errorcallback) { + auto cmdline = OsGetCommandLine(); + + return SCmdProcess(cmdline, 1, extracallback, errorcallback); +} + +uint32_t SCmdGetNum(uint32_t id) { + for (int32_t flaglist = 0; flaglist <= 1; flaglist++) { + auto& list = flaglist ? s_flaglist : s_arglist; + + for (auto def = list.Head(); def; def = def->Next()) { + if (def->id == id) { + return def->currvalue; + } + } + } + + return 0; +} + +int32_t SCmdGetBool(uint32_t id) { + return SCmdGetNum(id) != 0; +} + +int32_t SCmdGetString(uint32_t id, char* buffer, uint32_t bufferchars) { + if (buffer) { + *buffer = '\0'; + } + + STORM_VALIDATE(buffer, ERROR_INVALID_PARAMETER, 0); + STORM_VALIDATE(bufferchars, ERROR_INVALID_PARAMETER, 0); + + for (int32_t flaglist = 0; flaglist <= 1; flaglist++) { + auto& list = flaglist ? s_flaglist : s_arglist; + + for (auto def = list.Head(); def; def = def->Next()) { + if (def->id == id) { + if (def->currvaluestr) { + SStrCopy(buffer, def->currvaluestr, bufferchars); + } + + return 1; + } + } + } + + return 0; +} diff --git a/storm/Command.hpp b/storm/Command.hpp new file mode 100644 index 0000000..1b52388 --- /dev/null +++ b/storm/Command.hpp @@ -0,0 +1,125 @@ +#ifndef STORM_COMMAND_HPP +#define STORM_COMMAND_HPP + +#include "storm/List.hpp" + +#include + +#define STORM_COMMAND_ERROR_BAD_ARGUMENT 0x85100065 +#define STORM_COMMAND_ERROR_NOT_ENOUGH_ARGUMENTS 0x8510006D +#define STORM_COMMAND_ERROR_OPEN_FAILED 0x6E + +#define STORM_COMMAND_WHITESPACE_CHARS " ,;\"\t\n\r\x1A" + +#define STORM_COMMAND_EXTRA_CASE_SENSITIVE (1 << 8) + +// Type flags +#define STORM_COMMAND_TYPE_BOOL (0 << 16) +#define STORM_COMMAND_TYPE_NUMBER (1 << 16) +#define STORM_COMMAND_TYPE_STRING (2 << 16) +#define STORM_COMMAND_TYPE_MASK (STORM_COMMAND_TYPE_BOOL | STORM_COMMAND_TYPE_NUMBER | STORM_COMMAND_TYPE_STRING) + +// Bool value +#define STORM_COMMAND_BOOL_SET 0 +#define STORM_COMMAND_BOOL_CLEAR 1 +#define STORM_COMMAND_BOOL_MASK (STORM_COMMAND_BOOL_CLEAR | STORM_COMMAND_BOOL_SET) + +// Numeric value +#define STORM_COMMAND_NUM_UNSIGNED 0 +#define STORM_COMMAND_NUM_SIGNED 1 +#define STORM_COMMAND_NUM_MASK (STORM_COMMAND_NUM_UNSIGNED | STORM_COMMAND_NUM_SIGNED) + +// Argument +#define STORM_COMMAND_ARG_FLAGGED (0 << 24) +#define STORM_COMMAND_ARG_OPTIONAL (1 << 24) +#define STORM_COMMAND_ARG_REQUIRED (2 << 24) +#define STORM_COMMAND_ARG_MASK (STORM_COMMAND_ARG_FLAGGED | STORM_COMMAND_ARG_OPTIONAL | STORM_COMMAND_ARG_REQUIRED) + +// Getters +#define STORM_COMMAND_GET_TYPE(u32) (u32 & STORM_COMMAND_TYPE_MASK) +#define STORM_COMMAND_GET_ARG(u32) (u32 & STORM_COMMAND_ARG_MASK) +#define STORM_COMMAND_GET_BOOL(u32) (u32 & STORM_COMMAND_BOOL_MASK) +#define STORM_COMMAND_GET_NUM(u32) (u32 & STORM_COMMAND_NUM_MASK) + +class CMDERROR; +class CMDPARAMS; + +// Callback types +typedef int32_t (*CMDEXTRACALLBACKFCN)(const char*); +typedef int32_t (*CMDERRORCALLBACKFCN)(CMDERROR*); +typedef int32_t (*CMDPARAMSCALLBACKFCN)(CMDPARAMS*, const char*); + +// Details a command line argument +class ARGLIST { + public: + uint32_t flags; + uint32_t id; + const char* name; + CMDPARAMSCALLBACKFCN callback; +}; + +// Parameters passed to argument callback +class CMDPARAMS { + public: + uint32_t flags; + uint32_t id; + const char* name; + void* variable; + uint32_t setvalue; + uint32_t setmask; + union { + int32_t boolvalue; + int32_t signedvalue; + uint32_t unsignedvalue; + const char* stringvalue; + }; +}; + +// Command definitions +class CMDDEF : public TSLinkedNode { + public: + uint32_t flags; + uint32_t id; + char name[16]; + int32_t namelength; + uint32_t setvalue; + uint32_t setmask; + void* variableptr; + uint32_t variablebytes; + CMDPARAMSCALLBACKFCN callback; + int32_t found; + union { + uint32_t currvalue; + char* currvaluestr; + }; +}; + +class CMDERROR { + public: + uint32_t errorcode; + const char* itemstr; + const char* errorstr; +}; + +class PROCESSING { + public: + CMDDEF* ptr; + char name[16]; + int32_t namelength; +}; + +int32_t SCmdRegisterArgument(uint32_t flags, uint32_t id, const char* name, void* variableptr, uint32_t variablebytes, uint32_t setvalue, uint32_t setmask, CMDPARAMSCALLBACKFCN callback); + +int32_t SCmdRegisterArgList(ARGLIST* listptr, uint32_t numargs); + +int32_t SCmdProcessCommandLine(CMDEXTRACALLBACKFCN extracallback, CMDERRORCALLBACKFCN errorcallback); + +int32_t SCmdProcess(const char* cmdline, int32_t skipprogname, CMDEXTRACALLBACKFCN extracallback, CMDERRORCALLBACKFCN errorcallback); + +uint32_t SCmdGetNum(uint32_t id); + +int32_t SCmdGetBool(uint32_t id); + +int32_t SCmdGetString(uint32_t id, char* buffer, uint32_t bufferchars); + +#endif