Improved command processing.

This commit is contained in:
2025-03-28 15:20:15 -05:00
parent 2135217843
commit 4eb933c3f6
5 changed files with 215 additions and 122 deletions

View File

@ -14,15 +14,19 @@ console_t CONSOLE;
void consoleInit() { void consoleInit() {
memoryZero(&CONSOLE, sizeof(console_t)); memoryZero(&CONSOLE, sizeof(console_t));
pthread_mutex_init(&CONSOLE.lock, NULL); // Initialize the mutex
// Register the get and set command. // Register the get and set command.
CONSOLE.cmdGet = consoleRegCmd("get", consoleCmdGet); CONSOLE.cmdGet = consoleRegCmd("get", cmdGet);
CONSOLE.cmdSet = consoleRegCmd("set", consoleCmdSet); CONSOLE.cmdSet = consoleRegCmd("set", cmdSet);
consoleRegCmd("echo", cmdEcho);
} }
consolecmd_t * consoleRegCmd(const char_t *name, consolecmdfunc_t function) { consolecmd_t * consoleRegCmd(const char_t *name, consolecmdfunc_t function) {
pthread_mutex_lock(&CONSOLE.lock); // Lock
consolecmd_t *cmd = &CONSOLE.commands[CONSOLE.commandCount++]; consolecmd_t *cmd = &CONSOLE.commands[CONSOLE.commandCount++];
consoleCmdInit(cmd, name, function); consoleCmdInit(cmd, name, function);
pthread_mutex_unlock(&CONSOLE.lock); // Unlock
return cmd; return cmd;
} }
@ -31,8 +35,10 @@ consolevar_t * consoleRegVar(
const char_t *value, const char_t *value,
consolevarchanged_t event consolevarchanged_t event
) { ) {
pthread_mutex_lock(&CONSOLE.lock); // Lock
consolevar_t *var = &CONSOLE.variables[CONSOLE.variableCount++]; consolevar_t *var = &CONSOLE.variables[CONSOLE.variableCount++];
consoleVarInitListener(var, name, value, event); consoleVarInitListener(var, name, value, event);
pthread_mutex_unlock(&CONSOLE.lock); // Unlock
return var; return var;
} }
@ -62,163 +68,245 @@ void consolePrint(const char_t *message, ...) {
} }
void consoleExec(const char_t *line) { void consoleExec(const char_t *line) {
pthread_mutex_lock(&CONSOLE.lock); // Lock
assertNotNull(line, "line must not be NULL"); assertNotNull(line, "line must not be NULL");
assertTrue( assertTrue(
CONSOLE.execBufferCount < CONSOLE_EXEC_BUFFER_MAX, CONSOLE.execBufferCount < CONSOLE_EXEC_BUFFER_MAX,
"Too many commands in the buffer." "Too many commands in the buffer."
); );
if(line[0] == '\0') return;
char_t buffer[CONSOLE_LINE_MAX + 1]; char_t buffer[CONSOLE_LINE_MAX + 1];
size_t i = 0, j = 0; size_t i = 0, j = 0;
char_t c; char_t c;
consoleexecstate_t state = CONSOLE_EXEC_STATE_INITIAL;
consolecmdexec_t *exec = NULL;
do { while(state != CONSOLE_EXEC_STATE_FULLY_PARSED) {
c = line[i++]; c = line[i];
// Handle command separation by semicolon or end of line switch(state) {
if(c == ';' || c == '\0') { case CONSOLE_EXEC_STATE_INITIAL:
// Null-terminate the current command and trim whitespace assertTrue(j == 0, "Buffer not empty?");
buffer[j] = '\0';
stringTrim(buffer);
// Skip empty commands if(c == '\0') {
if(buffer[0] == '\0') { state = CONSOLE_EXEC_STATE_FULLY_PARSED;
j = 0; break;
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;
} }
exec->cmd = &CONSOLE.commands[k];
}
// If match found continue if(stringIsWhitespace(c) || c == ';') {
if(exec->cmd != NULL) { i++;
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) {
continue; continue;
} }
var = &CONSOLE.variables[k]; state = CONSOLE_EXEC_STATE_PARSE_CMD;
break; break;
}
// If no variable found, error out. case CONSOLE_EXEC_STATE_PARSE_CMD:
if(!var) { if(stringIsWhitespace(c) || c == '\0' || c == ';') {
consolePrint("Error: Command/Variable '%s' not found.", exec->argv[0]); state = CONSOLE_EXEC_STATE_CMD_PARSED;
CONSOLE.execBufferCount--; continue;
j = 0; }
continue;
}
// Found a matching variable, we basically change this entire command if(c == '"') {
// to a get or a set. Arguments are reversed so they aren't stepped over // Can't handle quotes within the command.
if(exec->argc == 1) { consolePrint("Error: Invalid command.");
exec->cmd = CONSOLE.cmdGet; while(c != '\0' && c != ';') c = line[++i];
exec->argc = 2; continue;
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);
}
j = 0; assertTrue(j < CONSOLE_LINE_MAX, "Command is too long.");
continue; 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) { pthread_mutex_unlock(&CONSOLE.lock); // Unlock
assertTrue(false, "Command exceeds maximum length.");
}
buffer[j++] = c; // Accumulate characters into the buffer
} while(c != '\0');
} }
void consoleProcess() { void consoleProcess() {
pthread_mutex_lock(&CONSOLE.lock); // Lock
for(uint32_t i = 0; i < CONSOLE.execBufferCount; i++) { for(uint32_t i = 0; i < CONSOLE.execBufferCount; i++) {
consolecmdexec_t *exec = &CONSOLE.execBuffer[i]; consolecmdexec_t *exec = &CONSOLE.execBuffer[i];
assertTrue( assertNotNull(exec->cmd, "Command execution has no command.");
exec->argc > 0,
"Command execution has no arguments."
);
assertNotNull(
exec->cmd,
"Command execution has no command."
);
exec->cmd->function(exec); exec->cmd->function(exec);
} }
// Clear the exec buffer // Clear the exec buffer
CONSOLE.execBufferCount = 0; CONSOLE.execBufferCount = 0;
pthread_mutex_unlock(&CONSOLE.lock); // Unlock
} }
void consoleCmdGet(const consolecmdexec_t *exec) { void cmdGet(const consolecmdexec_t *exec) {
assertTrue( assertTrue(
exec->argc >= 2, exec->argc >= 1,
"get command requires 1 argument." "Get command requires 1 argument."
); );
for(uint32_t i = 0; i < CONSOLE.variableCount; i++) { for(uint32_t i = 0; i < CONSOLE.variableCount; i++) {
consolevar_t *var = &CONSOLE.variables[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); consolePrint("%s", var->value);
return; 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( assertTrue(
exec->argc >= 3, exec->argc >= 2,
"set command requires 2 arguments." "set command requires 2 arguments."
); );
for(uint32_t i = 0; i < CONSOLE.variableCount; i++) { for(uint32_t i = 0; i < CONSOLE.variableCount; i++) {
consolevar_t *var = &CONSOLE.variables[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;
consoleVarSetValue(var, exec->argv[2]); consoleVarSetValue(var, exec->argv[1]);
return; 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]);
} }

View File

@ -9,6 +9,20 @@
#include "consolevar.h" #include "consolevar.h"
#include "consolecmd.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 { typedef struct {
consolecmd_t commands[CONSOLE_COMMANDS_MAX]; consolecmd_t commands[CONSOLE_COMMANDS_MAX];
uint32_t commandCount; uint32_t commandCount;
@ -23,6 +37,7 @@ typedef struct {
consolecmd_t *cmdGet; consolecmd_t *cmdGet;
consolecmd_t *cmdSet; consolecmd_t *cmdSet;
pthread_mutex_t lock; // Mutex for thread safety
} console_t; } console_t;
extern console_t CONSOLE; extern console_t CONSOLE;
@ -78,18 +93,6 @@ void consoleExec(const char_t *line);
*/ */
void consoleProcess(); void consoleProcess();
/** void cmdGet(const consolecmdexec_t *exec);
* Gets the value of a console variable. void cmdSet(const consolecmdexec_t *exec);
* void cmdEcho(const consolecmdexec_t *exec);
* @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);

View File

@ -12,9 +12,10 @@
typedef struct consolecmd_s consolecmd_t; typedef struct consolecmd_s consolecmd_t;
typedef struct { typedef struct {
const consolecmd_t *cmd; consolecmd_t *cmd;
uint32_t argc; char_t command[CONSOLE_LINE_MAX];
char_t argv[CONSOLE_CMD_ARGC_MAX][CONSOLE_LINE_MAX]; char_t argv[CONSOLE_CMD_ARGC_MAX][CONSOLE_LINE_MAX];
uint32_t argc;
} consolecmdexec_t; } consolecmdexec_t;
typedef void (*consolecmdfunc_t)(const consolecmdexec_t *exec); typedef void (*consolecmdfunc_t)(const consolecmdexec_t *exec);

View File

@ -15,6 +15,7 @@
#include <stdio.h> #include <stdio.h>
#include <stdarg.h> #include <stdarg.h>
#include <ctype.h> #include <ctype.h>
#include <pthread.h>
typedef bool bool_t; typedef bool bool_t;
typedef char char_t; typedef char char_t;