Improved command processing.
This commit is contained in:
@ -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;
|
|
||||||
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(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) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var = &CONSOLE.variables[k];
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no variable found, error out.
|
if(stringIsWhitespace(c) || c == ';') {
|
||||||
if(!var) {
|
i++;
|
||||||
consolePrint("Error: Command/Variable '%s' not found.", exec->argv[0]);
|
|
||||||
CONSOLE.execBufferCount--;
|
|
||||||
j = 0;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Found a matching variable, we basically change this entire command
|
state = CONSOLE_EXEC_STATE_PARSE_CMD;
|
||||||
// to a get or a set. Arguments are reversed so they aren't stepped over
|
break;
|
||||||
if(exec->argc == 1) {
|
|
||||||
|
case CONSOLE_EXEC_STATE_PARSE_CMD:
|
||||||
|
if(stringIsWhitespace(c) || c == '\0' || c == ';') {
|
||||||
|
state = CONSOLE_EXEC_STATE_CMD_PARSED;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(c == '"') {
|
||||||
|
// Can't handle quotes within the command.
|
||||||
|
consolePrint("Error: Invalid command.");
|
||||||
|
while(c != '\0' && c != ';') c = line[++i];
|
||||||
|
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;
|
exec->cmd = CONSOLE.cmdGet;
|
||||||
exec->argc = 2;
|
stringCopy(exec->argv[0], exec->command, CONSOLE_LINE_MAX);
|
||||||
stringCopy(exec->argv[1], var->name, CONSOLE_LINE_MAX);
|
exec->argc = 1;
|
||||||
stringCopy(exec->argv[0], CONSOLE.cmdGet->name, CONSOLE_LINE_MAX);
|
|
||||||
} else {
|
} else {
|
||||||
exec->cmd = CONSOLE.cmdSet;
|
exec->cmd = CONSOLE.cmdSet;
|
||||||
exec->argc = 3;
|
stringCopy(exec->argv[1], exec->argv[0], CONSOLE_LINE_MAX);
|
||||||
stringCopy(exec->argv[2], exec->argv[1], CONSOLE_LINE_MAX);
|
stringCopy(exec->argv[0], exec->command, CONSOLE_LINE_MAX);
|
||||||
stringCopy(exec->argv[1], var->name, CONSOLE_LINE_MAX);
|
exec->argc = 2;
|
||||||
stringCopy(exec->argv[0], CONSOLE.cmdSet->name, CONSOLE_LINE_MAX);
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
j = 0;
|
if(exec->cmd == NULL) {
|
||||||
continue;
|
consolePrint("Error: Command '%s' not found.", exec->command);
|
||||||
|
exec = NULL;
|
||||||
|
state = CONSOLE_EXEC_STATE_INITIAL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(j >= CONSOLE_LINE_MAX) {
|
// Prep for next command.
|
||||||
assertTrue(false, "Command exceeds maximum length.");
|
exec = NULL;
|
||||||
|
state = CONSOLE_EXEC_STATE_INITIAL;
|
||||||
|
CONSOLE.execBufferCount++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
assertUnreachable("Invalid state.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer[j++] = c; // Accumulate characters into the buffer
|
pthread_mutex_unlock(&CONSOLE.lock); // Unlock
|
||||||
} 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]);
|
||||||
}
|
}
|
@ -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);
|
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user