Add more stuff to the debugger
This commit is contained in:
@@ -3,6 +3,9 @@
|
|||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
typedef struct DebugCommand {
|
typedef struct DebugCommand {
|
||||||
char* command;
|
char* command;
|
||||||
@@ -30,84 +33,392 @@ static inline DebugCommand parseCommand(char* input) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
typedef struct Debugger {
|
typedef struct Debugger {
|
||||||
|
|
||||||
GroundSize currentInst;
|
GroundSize currentInst;
|
||||||
|
bool* breakpoints;
|
||||||
|
GroundSize programLen;
|
||||||
|
bool hasStarted;
|
||||||
|
bool hasFinished;
|
||||||
} Debugger;
|
} Debugger;
|
||||||
|
|
||||||
enum DebuggerStatus {
|
enum DebuggerStatus {
|
||||||
DBG_DONE, DBG_CONTINUE, DBG_WAIT, DBG_STEP
|
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) {
|
static inline enum DebuggerStatus runCommand(char* input, GroundBytecode* bytecode, Debugger* debugger) {
|
||||||
|
char* inputCopy = strdup(input);
|
||||||
DebugCommand cmd = parseCommand(input);
|
DebugCommand cmd = parseCommand(input);
|
||||||
|
|
||||||
|
if (cmd.command == NULL) {
|
||||||
|
free(inputCopy);
|
||||||
|
return DBG_WAIT;
|
||||||
|
}
|
||||||
|
|
||||||
if (strcmp(cmd.command, "help") == 0) {
|
if (strcmp(cmd.command, "help") == 0) {
|
||||||
printf(
|
printf(
|
||||||
"Commands: \n" \
|
"Commands: \n"
|
||||||
" help - Show this help message\n" \
|
" help - Show this help message\n"
|
||||||
" continue - Run the program until next breakpoint, error, or otherwise termination\n" \
|
" start - Start or restart program execution\n"
|
||||||
" step - Run one instruction\n" \
|
" continue - Run the program until next breakpoint, error, or otherwise termination\n"
|
||||||
" break [idx] - Sets a breakpoint on the specified index\n" \
|
" step - Run one instruction\n"
|
||||||
" exit - Stop debugging and exit\n\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" \
|
" dump - Dump current state of VM\n"
|
||||||
" view [slot] - Prints the variable in the current slot\n" \
|
" view [slot] - Prints the variable in the current slot\n"
|
||||||
" set [slot] [value] - Sets the current slot to the specified value\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" \
|
" 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) {
|
if (strcmp(cmd.command, "continue") == 0) {
|
||||||
|
free(inputCopy);
|
||||||
return DBG_CONTINUE;
|
return DBG_CONTINUE;
|
||||||
}
|
}
|
||||||
if (strcmp(cmd.command, "step") == 0) {
|
if (strcmp(cmd.command, "step") == 0) {
|
||||||
|
free(inputCopy);
|
||||||
return DBG_STEP;
|
return DBG_STEP;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strcmp(cmd.command, "break") == 0) {
|
if (strcmp(cmd.command, "break") == 0) {
|
||||||
|
if (cmd.args.count < 1) {
|
||||||
|
printf("Usage: break [idx]\n");
|
||||||
|
free(inputCopy);
|
||||||
return DBG_WAIT;
|
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) {
|
if (strcmp(cmd.command, "exit") == 0) {
|
||||||
|
free(inputCopy);
|
||||||
return DBG_DONE;
|
return DBG_DONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strcmp(cmd.command, "dump") == 0) {
|
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;
|
return DBG_WAIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strcmp(cmd.command, "view") == 0) {
|
if (strcmp(cmd.command, "view") == 0) {
|
||||||
|
if (cmd.args.count < 1) {
|
||||||
|
printf("Usage: view [slot]\n");
|
||||||
|
free(inputCopy);
|
||||||
return DBG_WAIT;
|
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 (strcmp(cmd.command, "set") == 0) {
|
||||||
|
if (cmd.args.count < 2) {
|
||||||
|
printf("Usage: set [slot] [value]\n");
|
||||||
|
free(inputCopy);
|
||||||
return DBG_WAIT;
|
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 (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;
|
return DBG_WAIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
fprintf(stderr, "Unknown command %s\n", cmd.command);
|
fprintf(stderr, "Unknown command %s\n", cmd.command);
|
||||||
|
free(inputCopy);
|
||||||
return DBG_WAIT;
|
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) {
|
void runDebugger(GroundBytecode* bytecode) {
|
||||||
printf("Ground Debugger\n");
|
printf("Ground Debugger\n");
|
||||||
printf("Type 'help' for a list of commands\n");
|
printf("Type 'help' for a list of commands\n");
|
||||||
@@ -115,23 +426,149 @@ void runDebugger(GroundBytecode* bytecode) {
|
|||||||
printf("Type 'exit' to exit\n");
|
printf("Type 'exit' to exit\n");
|
||||||
|
|
||||||
Debugger debugger;
|
Debugger debugger;
|
||||||
|
debugger.currentInst = 0;
|
||||||
|
debugger.programLen = bytecode->program.len;
|
||||||
|
debugger.breakpoints = calloc(debugger.programLen, sizeof(bool));
|
||||||
|
debugger.hasStarted = false;
|
||||||
|
debugger.hasFinished = false;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
char* input = linenoise("> ");
|
char* input = linenoise("> ");
|
||||||
if (input == NULL) {
|
if (input == NULL) {
|
||||||
continue;
|
free(debugger.breakpoints);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (input[0] != '\0') {
|
||||||
|
linenoiseHistoryAdd(input);
|
||||||
|
}
|
||||||
|
|
||||||
switch (runCommand(input, bytecode, &debugger)) {
|
switch (runCommand(input, bytecode, &debugger)) {
|
||||||
case DBG_DONE: {
|
case DBG_DONE: {
|
||||||
|
free(debugger.breakpoints);
|
||||||
|
free(input);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case DBG_CONTINUE: {
|
case DBG_CONTINUE: {
|
||||||
|
if (debugger.hasFinished) {
|
||||||
|
printf("Program has finished execution. Use 'start' to restart.\n");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case DBG_WAIT: {
|
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;
|
break;
|
||||||
}
|
}
|
||||||
case DBG_STEP: {
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user