Add more stuff to the debugger

This commit is contained in:
2026-07-28 18:40:33 +10:00
parent 7020ac3744
commit 8fe67ddc2b

View File

@@ -3,6 +3,9 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <inttypes.h>
typedef struct DebugCommand {
char* command;
@@ -30,84 +33,392 @@ static inline DebugCommand parseCommand(char* input) {
}
typedef struct Debugger {
GroundSize currentInst;
bool* breakpoints;
GroundSize programLen;
bool hasStarted;
bool hasFinished;
} Debugger;
enum DebuggerStatus {
DBG_DONE, DBG_CONTINUE, DBG_WAIT, DBG_STEP
};
static void printInstructionAt(GroundBytecode* bytecode, GroundSize idx) {
if (idx >= bytecode->program.len) {
printf("Index out of bounds\n");
return;
}
GroundBytecodeInstruction* instruction = &bytecode->program.at[idx];
const char* name = "UNKNOWN";
switch (instruction->type) {
case GroundInstruction_IF: name = "IF"; break;
case GroundInstruction_JUMP: name = "JUMP"; break;
case GroundInstruction_END: name = "END"; break;
case GroundInstruction_INPUT: name = "INPUT"; break;
case GroundInstruction_PRINT: name = "PRINT"; break;
case GroundInstruction_PRINTLN: name = "PRINTLN"; break;
case GroundInstruction_SET: name = "SET"; break;
case GroundInstruction_GETTYPE: name = "GETTYPE"; break;
case GroundInstruction_EXISTS: name = "EXISTS"; break;
case GroundInstruction_SETLIST: name = "SETLIST"; break;
case GroundInstruction_SETLISTAT: name = "SETLISTAT"; break;
case GroundInstruction_GETLISTAT: name = "GETLISTAT"; break;
case GroundInstruction_GETLISTSIZE: name = "GETLISTSIZE"; break;
case GroundInstruction_LISTAPPEND: name = "LISTAPPEND"; break;
case GroundInstruction_GETSTRSIZE: name = "GETSTRSIZE"; break;
case GroundInstruction_GETSTRCHARAT: name = "GETSTRCHARAT"; break;
case GroundInstruction_ADD: name = "ADD"; break;
case GroundInstruction_SUBTRACT: name = "SUBTRACT"; break;
case GroundInstruction_MULTIPLY: name = "MULTIPLY"; break;
case GroundInstruction_DIVIDE: name = "DIVIDE"; break;
case GroundInstruction_EQUAL: name = "EQUAL"; break;
case GroundInstruction_INEQUAL: name = "INEQUAL"; break;
case GroundInstruction_NOT: name = "NOT"; break;
case GroundInstruction_GREATER: name = "GREATER"; break;
case GroundInstruction_LESSER: name = "LESSER"; break;
case GroundInstruction_AND: name = "AND"; break;
case GroundInstruction_OR: name = "OR"; break;
case GroundInstruction_XOR: name = "XOR"; break;
case GroundInstruction_NEG: name = "NEG"; break;
case GroundInstruction_SHIFT: name = "SHIFT"; break;
case GroundInstruction_STOI: name = "STOI"; break;
case GroundInstruction_STOD: name = "STOD"; break;
case GroundInstruction_ITOC: name = "ITOC"; break;
case GroundInstruction_CTOI: name = "CTOI"; break;
case GroundInstruction_TOSTRING: name = "TOSTRING"; break;
case GroundInstruction_FUN: name = "FUN"; break;
case GroundInstruction_RETURN: name = "RETURN"; break;
case GroundInstruction_ENDFUN: name = "ENDFUN"; break;
case GroundInstruction_CALL: name = "CALL"; break;
case GroundInstruction_CALLMETHOD: name = "CALLMETHOD"; break;
case GroundInstruction_STRUCT: name = "STRUCT"; break;
case GroundInstruction_ENDSTRUCT: name = "ENDSTRUCT"; break;
case GroundInstruction_INIT: name = "INIT"; break;
case GroundInstruction_GETFIELD: name = "GETFIELD"; break;
case GroundInstruction_SETFIELD: name = "SETFIELD"; break;
case GroundInstruction_USE: name = "USE"; break;
case GroundInstruction_EXTERN: name = "EXTERN"; break;
case GroundInstruction_CREATELABEL: name = "CREATELABEL"; break;
case GroundInstruction_PAUSE: name = "PAUSE"; break;
case GroundInstruction_DROP: name = "DROP"; break;
case GroundInstruction_LICENSE: name = "LICENSE"; break;
case GroundInstruction_ERROR: name = "ERROR"; break;
case GroundInstruction_THROW: name = "THROW"; break;
case GroundInstruction_CATCH: name = "CATCH"; break;
}
printf(" %zu: %s ", idx, name);
for (GroundSize i = 0; i < instruction->args.len; i++) {
printf("%zu ", instruction->args.at[i]);
}
printf("\n");
}
static inline bool parseValue(const char* str, GroundBytecodeValue* outVal) {
enum GroundTypeType inferredType = GroundType_Undefined;
size_t len = strlen(str);
if (strcmp(str, "true") == 0 || strcmp(str, "false") == 0) {
inferredType = GroundType_Bool;
} else if (len >= 2 && str[0] == '"' && str[len - 1] == '"') {
inferredType = GroundType_String;
} else if (len >= 2 && str[0] == '\'' && str[len - 1] == '\'') {
if (len == 3) {
inferredType = GroundType_Char;
} else {
inferredType = GroundType_String;
}
} else {
// check if double or int
char* endptr;
bool has_dot = false;
for (const char* p = str; *p; p++) {
if (*p == '.' || *p == 'e' || *p == 'E') {
has_dot = true;
break;
}
}
if (has_dot) {
strtod(str, &endptr);
if (endptr != str && *endptr == '\0') {
inferredType = GroundType_Double;
}
} else {
strtoll(str, &endptr, 10);
if (endptr != str && *endptr == '\0') {
inferredType = GroundType_Int;
}
}
}
if (inferredType == GroundType_Undefined) {
inferredType = GroundType_String;
}
outVal->type.type = inferredType;
switch (inferredType) {
case GroundType_Int: {
char* endptr;
outVal->as.Int = strtoll(str, &endptr, 10);
if (endptr == str || *endptr != '\0') {
return false;
}
return true;
}
case GroundType_Double: {
char* endptr;
outVal->as.Double = strtod(str, &endptr);
if (endptr == str || *endptr != '\0') {
return false;
}
return true;
}
case GroundType_Bool: {
if (strcmp(str, "true") == 0 || strcmp(str, "1") == 0) {
outVal->as.Bool = true;
return true;
} else if (strcmp(str, "false") == 0 || strcmp(str, "0") == 0) {
outVal->as.Bool = false;
return true;
}
return false;
}
case GroundType_Char: {
if (str[0] == '\'' && str[1] != '\0' && str[2] == '\'' && str[3] == '\0') {
outVal->as.Char = str[1];
} else {
outVal->as.Char = str[0];
}
return true;
}
case GroundType_String: {
if (len >= 2 && str[0] == '"' && str[len - 1] == '"') {
outVal->as.String.len = len - 2;
outVal->as.String.cstr = malloc(len - 1);
if (outVal->as.String.cstr) {
memcpy(outVal->as.String.cstr, str + 1, len - 2);
outVal->as.String.cstr[len - 2] = '\0';
}
} else if (len >= 2 && str[0] == '\'' && str[len - 1] == '\'') {
outVal->as.String.len = len - 2;
outVal->as.String.cstr = malloc(len - 1);
if (outVal->as.String.cstr) {
memcpy(outVal->as.String.cstr, str + 1, len - 2);
outVal->as.String.cstr[len - 2] = '\0';
}
} else {
outVal->as.String.len = len;
outVal->as.String.cstr = malloc(len + 1);
if (outVal->as.String.cstr) {
strcpy(outVal->as.String.cstr, str);
}
}
return outVal->as.String.cstr != NULL;
}
default:
return false;
}
}
static inline enum DebuggerStatus runCommand(char* input, GroundBytecode* bytecode, Debugger* debugger) {
char* inputCopy = strdup(input);
DebugCommand cmd = parseCommand(input);
if (cmd.command == NULL) {
free(inputCopy);
return DBG_WAIT;
}
if (strcmp(cmd.command, "help") == 0) {
printf(
"Commands: \n" \
" help - Show this help message\n" \
" continue - Run the program until next breakpoint, error, or otherwise termination\n" \
" step - Run one instruction\n" \
" break [idx] - Sets a breakpoint on the specified index\n" \
" exit - Stop debugging and exit\n\n" \
"Commands: \n"
" help - Show this help message\n"
" start - Start or restart program execution\n"
" continue - Run the program until next breakpoint, error, or otherwise termination\n"
" step - Run one instruction\n"
" break [idx] - Sets a breakpoint on the specified index\n"
" exit - Stop debugging and exit\n\n"
" dump - Dump current state of VM\n" \
" view [slot] - Prints the variable in the current slot\n" \
" set [slot] [value] - Sets the current slot to the specified value\n" \
" copy [slot] [slot] - Copies a value from the second slot to the first\n" \
" dump - Dump current state of VM\n"
" view [slot] - Prints the variable in the current slot\n"
" set [slot] [value] - Sets the current slot to the specified value\n"
" copy [slot] [slot] - Copies a value from the second slot to the first\n"
);
return DBG_CONTINUE;
free(inputCopy);
return DBG_WAIT;
}
if (strcmp(cmd.command, "start") == 0) {
debugger->currentInst = 0;
debugger->hasStarted = true;
debugger->hasFinished = false;
printf("Started debugging program. Current instruction:\n");
printInstructionAt(bytecode, debugger->currentInst);
free(inputCopy);
return DBG_WAIT;
}
if (strcmp(cmd.command, "continue") == 0) {
free(inputCopy);
return DBG_CONTINUE;
}
if (strcmp(cmd.command, "step") == 0) {
free(inputCopy);
return DBG_STEP;
}
if (strcmp(cmd.command, "break") == 0) {
if (cmd.args.count < 1) {
printf("Usage: break [idx]\n");
free(inputCopy);
return DBG_WAIT;
}
char* endptr;
GroundSize idx = strtoull(cmd.args.at[0], &endptr, 10);
if (endptr == cmd.args.at[0] || *endptr != '\0') {
printf("Invalid instruction index\n");
free(inputCopy);
return DBG_WAIT;
}
if (idx >= bytecode->program.len) {
printf("Instruction index out of bounds (0 to %zu)\n", bytecode->program.len - 1);
free(inputCopy);
return DBG_WAIT;
}
debugger->breakpoints[idx] = true;
printf("Breakpoint set at instruction %zu\n", idx);
free(inputCopy);
return DBG_WAIT;
}
if (strcmp(cmd.command, "exit") == 0) {
free(inputCopy);
return DBG_DONE;
}
if (strcmp(cmd.command, "dump") == 0) {
printf("%s", Ground.Stringify.Bytecode(bytecode));
char* str = Ground.Stringify.Bytecode(bytecode);
if (str) {
printf("%s", str);
free(str);
}
free(inputCopy);
return DBG_WAIT;
}
if (strcmp(cmd.command, "view") == 0) {
if (cmd.args.count < 1) {
printf("Usage: view [slot]\n");
free(inputCopy);
return DBG_WAIT;
}
char* endptr;
GroundSize slot = strtoull(cmd.args.at[0], &endptr, 10);
if (endptr == cmd.args.at[0] || *endptr != '\0') {
printf("Invalid slot index\n");
free(inputCopy);
return DBG_WAIT;
}
if (slot >= bytecode->heap.len) {
printf("Slot index out of bounds (0 to %zu)\n", bytecode->heap.len - 1);
free(inputCopy);
return DBG_WAIT;
}
char* str = Ground.Stringify.BytecodeValue(&bytecode->heap.heap[slot]);
if (str) {
printf("Slot %zu: %s\n", slot, str);
free(str);
} else {
printf("Slot %zu: (failed to stringify)\n", slot);
}
free(inputCopy);
return DBG_WAIT;
}
if (strcmp(cmd.command, "set") == 0) {
if (cmd.args.count < 2) {
printf("Usage: set [slot] [value]\n");
free(inputCopy);
return DBG_WAIT;
}
char* endptr;
GroundSize slot = strtoull(cmd.args.at[0], &endptr, 10);
if (endptr == cmd.args.at[0] || *endptr != '\0') {
printf("Invalid slot index\n");
free(inputCopy);
return DBG_WAIT;
}
if (slot >= bytecode->heap.len) {
printf("Slot index out of bounds (0 to %zu)\n", bytecode->heap.len - 1);
free(inputCopy);
return DBG_WAIT;
}
char* p = strstr(inputCopy, cmd.args.at[0]);
if (p) {
p += strlen(cmd.args.at[0]);
while (*p == ' ') p++;
GroundBytecodeValue newVal;
memset(&newVal, 0, sizeof(newVal));
if (parseValue(p, &newVal)) {
Ground.Free.BytecodeValue(&bytecode->heap.heap[slot]);
bytecode->heap.heap[slot] = newVal;
char* val_repr = Ground.Stringify.BytecodeValue(&bytecode->heap.heap[slot]);
printf("Slot %zu set to %s\n", slot, val_repr ? val_repr : p);
if (val_repr) free(val_repr);
} else {
printf("Failed to parse value '%s'\n", p);
}
}
free(inputCopy);
return DBG_WAIT;
}
if (strcmp(cmd.command, "copy") == 0) {
if (cmd.args.count < 2) {
printf("Usage: copy [slot1] [slot2]\n");
free(inputCopy);
return DBG_WAIT;
}
char* endptr;
GroundSize slot1 = strtoull(cmd.args.at[0], &endptr, 10);
if (endptr == cmd.args.at[0] || *endptr != '\0') {
printf("Invalid slot1 index\n");
free(inputCopy);
return DBG_WAIT;
}
GroundSize slot2 = strtoull(cmd.args.at[1], &endptr, 10);
if (endptr == cmd.args.at[1] || *endptr != '\0') {
printf("Invalid slot2 index\n");
free(inputCopy);
return DBG_WAIT;
}
if (slot1 >= bytecode->heap.len || slot2 >= bytecode->heap.len) {
printf("Slot index out of bounds (0 to %zu)\n", bytecode->heap.len - 1);
free(inputCopy);
return DBG_WAIT;
}
Ground.Free.BytecodeValue(&bytecode->heap.heap[slot1]);
bytecode->heap.heap[slot1] = Ground.Copy.BytecodeValue(&bytecode->heap.heap[slot2]);
if (Ground.Flags.error) {
printf("Failed to copy slot %zu to slot %zu\n", slot2, slot1);
Ground.Flags.error = false;
} else {
char* val_repr = Ground.Stringify.BytecodeValue(&bytecode->heap.heap[slot1]);
printf("Copied slot %zu to slot %zu. New value: %s\n", slot2, slot1, val_repr ? val_repr : "");
if (val_repr) free(val_repr);
}
free(inputCopy);
return DBG_WAIT;
}
fprintf(stderr, "Unknown command %s\n", cmd.command);
free(inputCopy);
return DBG_WAIT;
}
static inline void runInstruction(GroundBytecodeProgram* program, GroundBytecodeHeap* heap) {
GroundSize i = 0;
while (i < program->len) {
struct GroundExecutionResult status = Ground.Bytecode.Instruction.execute(&program->at[i], heap);
if (Ground.Flags.error) {
fprintf(stderr, "Errors while debugging: \n");
Ground.Log.printErrors();
}
switch (status.type) {
case _GER_CONTINUE: i++; break;
case _GER_RETURN: return;
case _GER_JUMP: i = status.as.line; break;
case _GER_END: exit(status.as.end);
}
}
}
void runDebugger(GroundBytecode* bytecode) {
printf("Ground Debugger\n");
printf("Type 'help' for a list of commands\n");
@@ -115,23 +426,149 @@ void runDebugger(GroundBytecode* bytecode) {
printf("Type 'exit' to exit\n");
Debugger debugger;
debugger.currentInst = 0;
debugger.programLen = bytecode->program.len;
debugger.breakpoints = calloc(debugger.programLen, sizeof(bool));
debugger.hasStarted = false;
debugger.hasFinished = false;
for (;;) {
char* input = linenoise("> ");
if (input == NULL) {
continue;
free(debugger.breakpoints);
return;
}
if (input[0] != '\0') {
linenoiseHistoryAdd(input);
}
switch (runCommand(input, bytecode, &debugger)) {
case DBG_DONE: {
free(debugger.breakpoints);
free(input);
return;
}
case DBG_CONTINUE: {
break;
}
case DBG_WAIT: {
if (debugger.hasFinished) {
printf("Program has finished execution. Use 'start' to restart.\n");
break;
}
if (!debugger.hasStarted) {
debugger.hasStarted = true;
debugger.currentInst = 0;
printf("Started debugging program.\n");
}
bool first_step = true;
while (debugger.currentInst < bytecode->program.len) {
if (!first_step && debugger.breakpoints[debugger.currentInst]) {
printf("Breakpoint hit at instruction %zu\n", debugger.currentInst);
printInstructionAt(bytecode, debugger.currentInst);
break;
}
first_step = false;
struct GroundExecutionResult status = Ground.Bytecode.Instruction.execute(&bytecode->program.at[debugger.currentInst], &bytecode->heap);
if (Ground.Flags.error) {
fprintf(stderr, "Errors while debugging: \n");
Ground.Log.printErrors();
Ground.Flags.error = false;
break;
}
switch (status.type) {
case _GER_CONTINUE: {
debugger.currentInst++;
break;
}
case _GER_RETURN: {
char* val_str = Ground.Stringify.BytecodeValue(&status.as.value);
printf("Program returned value: %s\n", val_str ? val_str : "");
if (val_str) free(val_str);
debugger.hasFinished = true;
break;
}
case _GER_JUMP: {
debugger.currentInst = status.as.line;
break;
}
case _GER_END: {
printf("Program exited with code %" PRId64 "\n", status.as.end);
debugger.hasFinished = true;
break;
}
}
if (debugger.hasFinished) {
break;
}
}
if (debugger.currentInst >= bytecode->program.len && !debugger.hasFinished) {
printf("Program reached the end of instructions.\n");
debugger.hasFinished = true;
}
break;
}
case DBG_STEP: {
if (debugger.hasFinished) {
printf("Program has finished execution. Use 'start' to restart.\n");
break;
}
if (!debugger.hasStarted) {
debugger.hasStarted = true;
debugger.currentInst = 0;
printf("Started debugging program.\n");
}
if (debugger.currentInst >= bytecode->program.len) {
printf("Program reached the end of instructions.\n");
debugger.hasFinished = true;
break;
}
struct GroundExecutionResult status = Ground.Bytecode.Instruction.execute(&bytecode->program.at[debugger.currentInst], &bytecode->heap);
if (Ground.Flags.error) {
fprintf(stderr, "Errors while debugging: \n");
Ground.Log.printErrors();
Ground.Flags.error = false;
break;
}
switch (status.type) {
case _GER_CONTINUE: {
debugger.currentInst++;
break;
}
case _GER_RETURN: {
char* val_str = Ground.Stringify.BytecodeValue(&status.as.value);
printf("Program returned value: %s\n", val_str ? val_str : "");
if (val_str) free(val_str);
debugger.hasFinished = true;
break;
}
case _GER_JUMP: {
debugger.currentInst = status.as.line;
break;
}
case _GER_END: {
printf("Program exited with code %" PRId64 "\n", status.as.end);
debugger.hasFinished = true;
break;
}
}
if (debugger.currentInst >= bytecode->program.len && !debugger.hasFinished) {
printf("Program reached the end of instructions.\n");
debugger.hasFinished = true;
} else if (!debugger.hasFinished) {
printInstructionAt(bytecode, debugger.currentInst);
}
break;
}
case DBG_WAIT: {
break;
}
}