diff --git a/src/cli/debugger.c b/src/cli/debugger.c index b8c0f84..491c275 100644 --- a/src/cli/debugger.c +++ b/src/cli/debugger.c @@ -3,6 +3,9 @@ #include #include +#include +#include +#include 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; } }