From 4eb933c3f6bd056c6f60ba3408fd3009ffa6e1e3 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Fri, 28 Mar 2025 15:20:15 -0500 Subject: [PATCH] Improved command processing. --- src/dusk/console/console.c | 296 ++++++++++++++++++++++------------ src/dusk/console/console.h | 33 ++-- src/dusk/console/consolecmd.h | 5 +- src/dusk/dusk.h | 1 + src/dusk/util/string.h | 2 +- 5 files changed, 215 insertions(+), 122 deletions(-) diff --git a/src/dusk/console/console.c b/src/dusk/console/console.c index 0141f59..598b182 100644 --- a/src/dusk/console/console.c +++ b/src/dusk/console/console.c @@ -14,15 +14,19 @@ console_t CONSOLE; void consoleInit() { memoryZero(&CONSOLE, sizeof(console_t)); + pthread_mutex_init(&CONSOLE.lock, NULL); // Initialize the mutex // Register the get and set command. - CONSOLE.cmdGet = consoleRegCmd("get", consoleCmdGet); - CONSOLE.cmdSet = consoleRegCmd("set", consoleCmdSet); + CONSOLE.cmdGet = consoleRegCmd("get", cmdGet); + CONSOLE.cmdSet = consoleRegCmd("set", cmdSet); + consoleRegCmd("echo", cmdEcho); } consolecmd_t * consoleRegCmd(const char_t *name, consolecmdfunc_t function) { + pthread_mutex_lock(&CONSOLE.lock); // Lock consolecmd_t *cmd = &CONSOLE.commands[CONSOLE.commandCount++]; consoleCmdInit(cmd, name, function); + pthread_mutex_unlock(&CONSOLE.lock); // Unlock return cmd; } @@ -31,8 +35,10 @@ consolevar_t * consoleRegVar( const char_t *value, consolevarchanged_t event ) { + pthread_mutex_lock(&CONSOLE.lock); // Lock consolevar_t *var = &CONSOLE.variables[CONSOLE.variableCount++]; consoleVarInitListener(var, name, value, event); + pthread_mutex_unlock(&CONSOLE.lock); // Unlock return var; } @@ -62,163 +68,245 @@ void consolePrint(const char_t *message, ...) { } void consoleExec(const char_t *line) { + pthread_mutex_lock(&CONSOLE.lock); // Lock assertNotNull(line, "line must not be NULL"); assertTrue( CONSOLE.execBufferCount < CONSOLE_EXEC_BUFFER_MAX, "Too many commands in the buffer." ); - if(line[0] == '\0') return; - char_t buffer[CONSOLE_LINE_MAX + 1]; size_t i = 0, j = 0; char_t c; + consoleexecstate_t state = CONSOLE_EXEC_STATE_INITIAL; + consolecmdexec_t *exec = NULL; - do { - c = line[i++]; + while(state != CONSOLE_EXEC_STATE_FULLY_PARSED) { + c = line[i]; - // Handle command separation by semicolon or end of line - if(c == ';' || c == '\0') { - // Null-terminate the current command and trim whitespace - buffer[j] = '\0'; - stringTrim(buffer); + switch(state) { + case CONSOLE_EXEC_STATE_INITIAL: + assertTrue(j == 0, "Buffer not empty?"); - // Skip empty commands - if(buffer[0] == '\0') { - j = 0; - continue; - } - - // Ensure the exec buffer is not full - assertTrue( - CONSOLE.execBufferCount < CONSOLE_EXEC_BUFFER_MAX, - "Too many commands in the buffer." - ); - - // Create a new command execution - consolecmdexec_t *exec = &CONSOLE.execBuffer[CONSOLE.execBufferCount++]; - memoryZero(exec, sizeof(consolecmdexec_t)); - - // Parse command and arguments - char_t *token = strtok(buffer, " "); - while(token != NULL) { - assertTrue( - exec->argc < CONSOLE_CMD_ARGC_MAX, - "Too many arguments in the command." - ); - stringCopy(exec->argv[exec->argc++], token, CONSOLE_LINE_MAX); - token = strtok(NULL, " "); - } - - // First, see if there's a matching command. - for(uint32_t k = 0; k < CONSOLE.commandCount; k++) { - if(stringCompare(CONSOLE.commands[k].name, exec->argv[0]) != 0) { - continue; + if(c == '\0') { + state = CONSOLE_EXEC_STATE_FULLY_PARSED; + break; } - exec->cmd = &CONSOLE.commands[k]; - } - // If match found continue - if(exec->cmd != NULL) { - j = 0; - continue; - } - - // If no match found, see if there's a matching variable. - consolevar_t *var = NULL; - - for(uint32_t k = 0; k < CONSOLE.variableCount; k++) { - if(stringCompare(CONSOLE.variables[k].name, exec->argv[0]) != 0) { + if(stringIsWhitespace(c) || c == ';') { + i++; continue; } - var = &CONSOLE.variables[k]; + state = CONSOLE_EXEC_STATE_PARSE_CMD; break; - } - // If no variable found, error out. - if(!var) { - consolePrint("Error: Command/Variable '%s' not found.", exec->argv[0]); - CONSOLE.execBufferCount--; - j = 0; - continue; - } + case CONSOLE_EXEC_STATE_PARSE_CMD: + if(stringIsWhitespace(c) || c == '\0' || c == ';') { + state = CONSOLE_EXEC_STATE_CMD_PARSED; + continue; + } - // Found a matching variable, we basically change this entire command - // to a get or a set. Arguments are reversed so they aren't stepped over - if(exec->argc == 1) { - exec->cmd = CONSOLE.cmdGet; - exec->argc = 2; - stringCopy(exec->argv[1], var->name, CONSOLE_LINE_MAX); - stringCopy(exec->argv[0], CONSOLE.cmdGet->name, CONSOLE_LINE_MAX); - } else { - exec->cmd = CONSOLE.cmdSet; - exec->argc = 3; - stringCopy(exec->argv[2], exec->argv[1], CONSOLE_LINE_MAX); - stringCopy(exec->argv[1], var->name, CONSOLE_LINE_MAX); - stringCopy(exec->argv[0], CONSOLE.cmdSet->name, CONSOLE_LINE_MAX); - } + if(c == '"') { + // Can't handle quotes within the command. + consolePrint("Error: Invalid command."); + while(c != '\0' && c != ';') c = line[++i]; + continue; + } - j = 0; - continue; + assertTrue(j < CONSOLE_LINE_MAX, "Command is too long."); + buffer[j++] = c; + i++; + break; + + case CONSOLE_EXEC_STATE_CMD_PARSED: + if(j == 0) { + state = CONSOLE_EXEC_STATE_INITIAL; + continue; + } + + // Create exec + assertNull(exec, "Existing command parsing?"); + + exec = &CONSOLE.execBuffer[CONSOLE.execBufferCount]; + memoryZero(exec, sizeof(consolecmdexec_t)); + + buffer[j] = '\0'; + stringCopy(exec->command, buffer, CONSOLE_LINE_MAX); + state = CONSOLE_EXEC_STATE_FIND_ARG; + + j = 0;// Free up buffer + break; + + case CONSOLE_EXEC_STATE_FIND_ARG: + + if(c == '\0' || c == ';') { + state = CONSOLE_EXEC_STATE_CMD_FINISHED; + continue; + } + + if(stringIsWhitespace(c)) { + i++; + continue; + } + + if(c == '"') { + state = CONSOLE_EXEC_STATE_PARSE_ARG_QUOTED; + i++; + } else { + state = CONSOLE_EXEC_STATE_PARSE_ARG; + } + break; + + case CONSOLE_EXEC_STATE_PARSE_ARG: + if(stringIsWhitespace(c) || c == '\0' || c == ';') { + state = CONSOLE_EXEC_STATE_ARG_PARSED; + continue; + } + + buffer[j++] = c; + i++; + break; + + case CONSOLE_EXEC_STATE_PARSE_ARG_QUOTED: + if(c == '"') { + state = CONSOLE_EXEC_STATE_ARG_PARSED; + i++; + continue; + } + + if(c == '\0' || c == ';') { + consolePrint("Error: Unterminated quoted argument."); + return; + } + + if(c == '\\') { + c = line[++i]; + + if(c == '\0' || c == ';') { + consolePrint("Error: Unterminated quoted argument."); + return; + } + } + + buffer[j++] = c; + i++; + break; + + case CONSOLE_EXEC_STATE_ARG_PARSED: + assertTrue(j != 0, "Empty argument?"); + buffer[j] = '\0'; + stringCopy(exec->argv[exec->argc++], buffer, CONSOLE_LINE_MAX); + state = CONSOLE_EXEC_STATE_FIND_ARG; + j = 0;// Free up buffer + break; + + case CONSOLE_EXEC_STATE_CMD_FINISHED: + assertNotNull(exec, "No command found?"); + + // Now, is there a command that matches? + for(uint32_t k = 0; k < CONSOLE.commandCount; k++) { + consolecmd_t *cmd = &CONSOLE.commands[k]; + if(stringCompare(cmd->name, exec->command) != 0) continue; + exec->cmd = cmd; + break; + } + + if(exec->cmd == NULL) { + // Command wasn't found, is there a variable that matches? + for(uint32_t k = 0; k < CONSOLE.variableCount; k++) { + consolevar_t *var = &CONSOLE.variables[k]; + if(stringCompare(var->name, exec->command) != 0) continue; + + // Matching variable found, is this a GET or a SET? + if(exec->argc == 0) { + exec->cmd = CONSOLE.cmdGet; + stringCopy(exec->argv[0], exec->command, CONSOLE_LINE_MAX); + exec->argc = 1; + } else { + exec->cmd = CONSOLE.cmdSet; + stringCopy(exec->argv[1], exec->argv[0], CONSOLE_LINE_MAX); + stringCopy(exec->argv[0], exec->command, CONSOLE_LINE_MAX); + exec->argc = 2; + } + break; + } + + if(exec->cmd == NULL) { + consolePrint("Error: Command '%s' not found.", exec->command); + exec = NULL; + state = CONSOLE_EXEC_STATE_INITIAL; + break; + } + } + + // Prep for next command. + exec = NULL; + state = CONSOLE_EXEC_STATE_INITIAL; + CONSOLE.execBufferCount++; + break; + + default: + assertUnreachable("Invalid state."); + break; } + } - if(j >= CONSOLE_LINE_MAX) { - assertTrue(false, "Command exceeds maximum length."); - } - - buffer[j++] = c; // Accumulate characters into the buffer - } while(c != '\0'); + pthread_mutex_unlock(&CONSOLE.lock); // Unlock } void consoleProcess() { + pthread_mutex_lock(&CONSOLE.lock); // Lock for(uint32_t i = 0; i < CONSOLE.execBufferCount; i++) { consolecmdexec_t *exec = &CONSOLE.execBuffer[i]; - assertTrue( - exec->argc > 0, - "Command execution has no arguments." - ); - assertNotNull( - exec->cmd, - "Command execution has no command." - ); + assertNotNull(exec->cmd, "Command execution has no command."); exec->cmd->function(exec); } // Clear the exec buffer CONSOLE.execBufferCount = 0; + pthread_mutex_unlock(&CONSOLE.lock); // Unlock } -void consoleCmdGet(const consolecmdexec_t *exec) { +void cmdGet(const consolecmdexec_t *exec) { assertTrue( - exec->argc >= 2, - "get command requires 1 argument." + exec->argc >= 1, + "Get command requires 1 argument." ); for(uint32_t i = 0; i < CONSOLE.variableCount; i++) { consolevar_t *var = &CONSOLE.variables[i]; - if(stringCompare(var->name, exec->argv[1]) != 0) continue; + if(stringCompare(var->name, exec->argv[0]) != 0) continue; consolePrint("%s", var->value); return; } - consolePrint("Error: Variable '%s' not found.", exec->argv[1]); + consolePrint("Error: Variable '%s' not found.", exec->argv[0]); } -void consoleCmdSet(const consolecmdexec_t *exec) { +void cmdSet(const consolecmdexec_t *exec) { assertTrue( - exec->argc >= 3, + exec->argc >= 2, "set command requires 2 arguments." ); for(uint32_t i = 0; i < CONSOLE.variableCount; i++) { consolevar_t *var = &CONSOLE.variables[i]; - if(stringCompare(var->name, exec->argv[1]) != 0) continue; - consoleVarSetValue(var, exec->argv[2]); + if(stringCompare(var->name, exec->argv[0]) != 0) continue; + consoleVarSetValue(var, exec->argv[1]); return; } - consolePrint("Error: Variable '%s' not found.", exec->argv[1]); + consolePrint("Error: Variable '%s' not found.", exec->argv[0]); +} + +void cmdEcho(const consolecmdexec_t *exec) { + assertTrue( + exec->argc >= 1, + "echo command requires 1 argument." + ); + + consolePrint("%s", exec->argv[0]); } \ No newline at end of file diff --git a/src/dusk/console/console.h b/src/dusk/console/console.h index 55875dd..acead31 100644 --- a/src/dusk/console/console.h +++ b/src/dusk/console/console.h @@ -9,6 +9,20 @@ #include "consolevar.h" #include "consolecmd.h" +typedef enum { + CONSOLE_EXEC_STATE_INITIAL, + CONSOLE_EXEC_STATE_PARSE_CMD, + CONSOLE_EXEC_STATE_CMD_PARSED, + + CONSOLE_EXEC_STATE_FIND_ARG, + CONSOLE_EXEC_STATE_PARSE_ARG, + CONSOLE_EXEC_STATE_PARSE_ARG_QUOTED, + CONSOLE_EXEC_STATE_ARG_PARSED, + + CONSOLE_EXEC_STATE_CMD_FINISHED, + CONSOLE_EXEC_STATE_FULLY_PARSED +} consoleexecstate_t; + typedef struct { consolecmd_t commands[CONSOLE_COMMANDS_MAX]; uint32_t commandCount; @@ -23,6 +37,7 @@ typedef struct { consolecmd_t *cmdGet; consolecmd_t *cmdSet; + pthread_mutex_t lock; // Mutex for thread safety } console_t; extern console_t CONSOLE; @@ -78,18 +93,6 @@ void consoleExec(const char_t *line); */ void consoleProcess(); -/** - * Gets the value of a console variable. - * - * @param name The name of the variable. - * @return The value of the variable. - */ -void consoleCmdGet(const consolecmdexec_t *exec); - -/** - * Sets the value of a console variable. - * - * @param name The name of the variable. - * @param value The new value of the variable. - */ -void consoleCmdSet(const consolecmdexec_t *exec); \ No newline at end of file +void cmdGet(const consolecmdexec_t *exec); +void cmdSet(const consolecmdexec_t *exec); +void cmdEcho(const consolecmdexec_t *exec); \ No newline at end of file diff --git a/src/dusk/console/consolecmd.h b/src/dusk/console/consolecmd.h index 04ec31c..6de2c02 100644 --- a/src/dusk/console/consolecmd.h +++ b/src/dusk/console/consolecmd.h @@ -12,9 +12,10 @@ typedef struct consolecmd_s consolecmd_t; typedef struct { - const consolecmd_t *cmd; - uint32_t argc; + consolecmd_t *cmd; + char_t command[CONSOLE_LINE_MAX]; char_t argv[CONSOLE_CMD_ARGC_MAX][CONSOLE_LINE_MAX]; + uint32_t argc; } consolecmdexec_t; typedef void (*consolecmdfunc_t)(const consolecmdexec_t *exec); diff --git a/src/dusk/dusk.h b/src/dusk/dusk.h index ca6cdd6..99a999d 100644 --- a/src/dusk/dusk.h +++ b/src/dusk/dusk.h @@ -15,6 +15,7 @@ #include #include #include +#include typedef bool bool_t; typedef char char_t; diff --git a/src/dusk/util/string.h b/src/dusk/util/string.h index d881605..aa5b51f 100644 --- a/src/dusk/util/string.h +++ b/src/dusk/util/string.h @@ -23,7 +23,7 @@ bool_t stringIsWhitespace(const char_t c); * @param dest The destination string. * @param src The source string. * @param destSize The size of the destination string exc. null terminator. - */ + */ void stringCopy(char_t *dest, const char_t *src, const size_t destSize); /**