// ideMain.c -- DVX BASIC Runner application // // A DVX app that loads, compiles, and runs BASIC programs. // PRINT output goes to a scrollable TextArea widget. Compile // errors are displayed with line numbers. // // This is Phase 3 of DVX BASIC: proving the compiler and VM // work on real hardware inside the DVX windowing system. #include "dvxApp.h" #include "dvxDialog.h" #include "dvxWidget.h" #include "dvxWm.h" #include "shellApp.h" #include "widgetBox.h" #include "widgetButton.h" #include "widgetLabel.h" #include "widgetTextInput.h" #include "widgetStatusBar.h" #include "../compiler/parser.h" #include "../runtime/vm.h" #include "../runtime/values.h" #include #include #include #include #include // ============================================================ // Constants // ============================================================ #define IDE_WIN_W 560 #define IDE_WIN_H 400 #define IDE_BTN_W 80 #define IDE_BTN_SPACING 8 #define IDE_MAX_SOURCE 65536 #define IDE_MAX_OUTPUT 32768 // Menu command IDs #define CMD_OPEN 100 #define CMD_RUN 101 #define CMD_STOP 102 #define CMD_CLEAR 103 #define CMD_EXIT 104 // ============================================================ // Prototypes // ============================================================ int32_t appMain(DxeAppContextT *ctx); static void buildWindow(void); static void clearOutput(void); static void compileAndRun(void); static void loadFile(void); static void onClose(WindowT *win); static void onMenu(WindowT *win, int32_t menuId); static void onOpenClick(WidgetT *w); static void onRunClick(WidgetT *w); static void onClearClick(WidgetT *w); static void printCallback(void *ctx, const char *text, bool newline); static bool inputCallback(void *ctx, const char *prompt, char *buf, int32_t bufSize); static bool doEventsCallback(void *ctx); static void setStatus(const char *text); // ============================================================ // Module state // ============================================================ static DxeAppContextT *sCtx = NULL; static AppContextT *sAc = NULL; static WindowT *sWin = NULL; static WidgetT *sEditor = NULL; // TextArea for source code static WidgetT *sOutput = NULL; // TextArea for program output static WidgetT *sStatus = NULL; // Status bar label static BasVmT *sVm = NULL; // VM instance (non-NULL while running) static char sSourceBuf[IDE_MAX_SOURCE]; static char sOutputBuf[IDE_MAX_OUTPUT]; static int32_t sOutputLen = 0; static char sFilePath[260]; // ============================================================ // App descriptor // ============================================================ AppDescriptorT appDescriptor = { .name = "DVX BASIC", .hasMainLoop = false, .multiInstance = false, .stackSize = SHELL_STACK_DEFAULT, .priority = 0 }; // ============================================================ // appMain // ============================================================ int32_t appMain(DxeAppContextT *ctx) { sCtx = ctx; sAc = ctx->shellCtx; basStringSystemInit(); buildWindow(); sFilePath[0] = '\0'; sSourceBuf[0] = '\0'; sOutputBuf[0] = '\0'; sOutputLen = 0; setStatus("Ready. Open a .BAS file or type code and press Run."); return 0; } // ============================================================ // buildWindow // ============================================================ static void buildWindow(void) { int32_t winX = (sAc->display.width - IDE_WIN_W) / 2; int32_t winY = (sAc->display.height - IDE_WIN_H) / 4; sWin = dvxCreateWindow(sAc, "DVX BASIC", winX, winY, IDE_WIN_W, IDE_WIN_H, true); if (!sWin) { return; } sWin->onClose = onClose; sWin->onMenu = onMenu; // Menu bar MenuBarT *menuBar = wmAddMenuBar(sWin); MenuT *fileMenu = wmAddMenu(menuBar, "&File"); wmAddMenuItem(fileMenu, "&Open...\tCtrl+O", CMD_OPEN); wmAddMenuSeparator(fileMenu); wmAddMenuItem(fileMenu, "E&xit", CMD_EXIT); MenuT *runMenu = wmAddMenu(menuBar, "&Run"); wmAddMenuItem(runMenu, "&Run\tF5", CMD_RUN); wmAddMenuSeparator(runMenu); wmAddMenuItem(runMenu, "&Clear Output", CMD_CLEAR); AccelTableT *accel = dvxCreateAccelTable(); dvxAddAccel(accel, 'O', ACCEL_CTRL, CMD_OPEN); dvxAddAccel(accel, KEY_F5, 0, CMD_RUN); sWin->accelTable = accel; // Widget tree WidgetT *root = wgtInitWindow(sAc, sWin); // Source code editor (top half) WidgetT *editorFrame = wgtFrame(root, "Source"); editorFrame->weight = 100; sEditor = wgtTextArea(editorFrame, IDE_MAX_SOURCE); sEditor->weight = 100; // Button bar WidgetT *btnRow = wgtHBox(root); btnRow->spacing = wgtPixels(IDE_BTN_SPACING); WidgetT *openBtn = wgtButton(btnRow, "&Open..."); openBtn->onClick = onOpenClick; openBtn->prefW = wgtPixels(IDE_BTN_W); WidgetT *runBtn = wgtButton(btnRow, "&Run"); runBtn->onClick = onRunClick; runBtn->prefW = wgtPixels(IDE_BTN_W); WidgetT *clearBtn = wgtButton(btnRow, "&Clear"); clearBtn->onClick = onClearClick; clearBtn->prefW = wgtPixels(IDE_BTN_W); // Output area (bottom half) WidgetT *outputFrame = wgtFrame(root, "Output"); outputFrame->weight = 100; sOutput = wgtTextArea(outputFrame, IDE_MAX_OUTPUT); sOutput->weight = 100; sOutput->readOnly = true; // Status bar WidgetT *statusBar = wgtStatusBar(root); sStatus = wgtLabel(statusBar, ""); sStatus->weight = 100; dvxFitWindow(sAc, sWin); } // ============================================================ // clearOutput // ============================================================ static void clearOutput(void) { sOutputBuf[0] = '\0'; sOutputLen = 0; wgtSetText(sOutput, ""); } // ============================================================ // compileAndRun // ============================================================ static void compileAndRun(void) { // Get source from editor const char *src = wgtGetText(sEditor); if (!src || *src == '\0') { setStatus("No source code to run."); return; } clearOutput(); setStatus("Compiling..."); // Force a display update so the status is visible dvxInvalidateWindow(sAc, sWin); int32_t srcLen = (int32_t)strlen(src); // Compile (heap-allocated -- BasParserT is ~300KB, too large for stack) BasParserT *parser = (BasParserT *)malloc(sizeof(BasParserT)); if (!parser) { setStatus("Out of memory."); return; } basParserInit(parser, src, srcLen); if (!basParse(parser)) { int32_t n = snprintf(sOutputBuf, IDE_MAX_OUTPUT, "COMPILE ERROR:\n%s\n", parser->error); sOutputLen = n; wgtSetText(sOutput, sOutputBuf); setStatus("Compilation failed."); basParserFree(parser); free(parser); return; } BasModuleT *mod = basParserBuildModule(parser); basParserFree(parser); free(parser); if (!mod) { setStatus("Failed to build module."); return; } setStatus("Running..."); // Create VM BasVmT *vm = basVmCreate(); basVmLoadModule(vm, mod); // Set up implicit main frame vm->callStack[0].localCount = mod->globalCount > BAS_VM_MAX_LOCALS ? BAS_VM_MAX_LOCALS : mod->globalCount; vm->callDepth = 1; // Set I/O callbacks basVmSetPrintCallback(vm, printCallback, NULL); basVmSetInputCallback(vm, inputCallback, NULL); basVmSetDoEventsCallback(vm, doEventsCallback, NULL); sVm = vm; // Run with step limit to prevent infinite loops from freezing the GUI vm->running = true; int32_t stepCount = 0; while (vm->running) { BasVmResultE result = basVmStep(vm); stepCount++; if (result != BAS_VM_OK) { if (result == BAS_VM_HALTED) { // Normal completion } else { // Runtime error int32_t pos = sOutputLen; int32_t n = snprintf(sOutputBuf + pos, IDE_MAX_OUTPUT - pos, "\n[Runtime error: %s]\n", basVmGetError(vm)); sOutputLen += n; wgtSetText(sOutput, sOutputBuf); } break; } // Yield to DVX every 10000 steps to keep the GUI responsive if (stepCount % 10000 == 0) { dvxUpdate(sAc); } } sVm = NULL; // Update output display wgtSetText(sOutput, sOutputBuf); static char statusBuf[128]; snprintf(statusBuf, sizeof(statusBuf), "Done. %ld instructions executed.", (long)stepCount); setStatus(statusBuf); basVmDestroy(vm); basModuleFree(mod); } // ============================================================ // doEventsCallback // ============================================================ static bool doEventsCallback(void *ctx) { (void)ctx; // Update DVX display and process events if (sAc) { dvxUpdate(sAc); } return true; // continue running } // ============================================================ // inputCallback // ============================================================ static bool inputCallback(void *ctx, const char *prompt, char *buf, int32_t bufSize) { (void)ctx; // Append prompt to output if (prompt && sOutputLen < IDE_MAX_OUTPUT - 1) { int32_t n = snprintf(sOutputBuf + sOutputLen, IDE_MAX_OUTPUT - sOutputLen, "%s", prompt); sOutputLen += n; wgtSetText(sOutput, sOutputBuf); } // Use DVX input dialog // For now, a simple message box prompt // TODO: proper INPUT dialog buf[0] = '\0'; return false; // cancel for now } // ============================================================ // loadFile // ============================================================ static void loadFile(void) { FileFilterT filters[] = { { "BASIC Files (*.bas)", "*.bas" }, { "Form Files (*.frm)", "*.frm" }, { "All Files (*.*)", "*.*" } }; char path[260]; if (dvxFileDialog(sAc, "Open BASIC File", FD_OPEN, NULL, filters, 3, path, sizeof(path))) { FILE *f = fopen(path, "r"); if (!f) { dvxMessageBox(sAc, "Error", "Could not open file.", MB_OK | MB_ICONERROR); return; } fseek(f, 0, SEEK_END); long size = ftell(f); fseek(f, 0, SEEK_SET); if (size >= IDE_MAX_SOURCE - 1) { fclose(f); dvxMessageBox(sAc, "Error", "File too large.", MB_OK | MB_ICONERROR); return; } int32_t bytesRead = (int32_t)fread(sSourceBuf, 1, size, f); fclose(f); sSourceBuf[bytesRead] = '\0'; wgtSetText(sEditor, sSourceBuf); strncpy(sFilePath, path, sizeof(sFilePath) - 1); sFilePath[sizeof(sFilePath) - 1] = '\0'; // Update window title char title[300]; snprintf(title, sizeof(title), "DVX BASIC - %s", path); dvxSetTitle(sAc, sWin, title); setStatus("File loaded."); } } // ============================================================ // onClearClick // ============================================================ static void onClearClick(WidgetT *w) { (void)w; clearOutput(); setStatus("Output cleared."); } // ============================================================ // onClose // ============================================================ static void onClose(WindowT *win) { sWin = NULL; sEditor = NULL; sOutput = NULL; sStatus = NULL; dvxDestroyWindow(sAc, win); } // ============================================================ // onMenu // ============================================================ static void onMenu(WindowT *win, int32_t menuId) { (void)win; switch (menuId) { case CMD_OPEN: loadFile(); break; case CMD_RUN: compileAndRun(); break; case CMD_CLEAR: clearOutput(); break; case CMD_EXIT: if (sWin) { onClose(sWin); } break; } } // ============================================================ // onOpenClick // ============================================================ static void onOpenClick(WidgetT *w) { (void)w; loadFile(); } // ============================================================ // onRunClick // ============================================================ static void onRunClick(WidgetT *w) { (void)w; compileAndRun(); } // ============================================================ // printCallback // ============================================================ static void printCallback(void *ctx, const char *text, bool newline) { (void)ctx; if (!text) { return; } int32_t textLen = (int32_t)strlen(text); // Append to output buffer if (sOutputLen + textLen < IDE_MAX_OUTPUT - 2) { memcpy(sOutputBuf + sOutputLen, text, textLen); sOutputLen += textLen; } if (newline && sOutputLen < IDE_MAX_OUTPUT - 2) { sOutputBuf[sOutputLen++] = '\n'; } sOutputBuf[sOutputLen] = '\0'; } // ============================================================ // setStatus // ============================================================ static void setStatus(const char *text) { if (sStatus) { wgtSetText(sStatus, text); } }