226 lines
5.8 KiB
C
226 lines
5.8 KiB
C
// basstub.c -- DVX BASIC standalone application stub
|
|
//
|
|
// Minimal entry point for compiled BASIC applications. Reads the
|
|
// MODULE resource (serialized bytecode), FORMn resources (.frm text),
|
|
// and optional DEBUG resource from its own DXE file. Creates a VM
|
|
// and form runtime from basrt.lib and runs the program.
|
|
//
|
|
// This file is compiled into basstub.app, which is embedded as a
|
|
// resource in dvxbasic.app. "Make Executable" extracts it, renames
|
|
// it to the project name, and attaches the compiled resources.
|
|
|
|
#include "dvxApp.h"
|
|
#include "dvxDlg.h"
|
|
#include "dvxRes.h"
|
|
#include "dvxWgt.h"
|
|
#include "shellApp.h"
|
|
#include "../runtime/vm.h"
|
|
#include "../runtime/serialize.h"
|
|
#include "../formrt/formrt.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <strings.h>
|
|
|
|
// ============================================================
|
|
// App descriptor
|
|
// ============================================================
|
|
|
|
#define STUB_STACK_SIZE 65536
|
|
|
|
AppDescriptorT appDescriptor = {
|
|
.name = "BASIC App",
|
|
.hasMainLoop = true,
|
|
.multiInstance = false,
|
|
.stackSize = STUB_STACK_SIZE,
|
|
.priority = TS_PRIORITY_NORMAL
|
|
};
|
|
|
|
|
|
// ============================================================
|
|
// Callbacks
|
|
// ============================================================
|
|
|
|
static AppContextT *sAc = NULL;
|
|
|
|
|
|
static void stubPrint(void *ctx, const char *text, bool newline) {
|
|
(void)ctx;
|
|
(void)text;
|
|
(void)newline;
|
|
}
|
|
|
|
|
|
static bool stubInput(void *ctx, const char *prompt, char *buf, int32_t bufSize) {
|
|
(void)ctx;
|
|
buf[0] = '\0';
|
|
|
|
if (!sAc) {
|
|
return false;
|
|
}
|
|
|
|
return dvxInputBox(sAc, "Input", prompt, "", buf, bufSize);
|
|
}
|
|
|
|
|
|
static bool stubDoEvents(void *ctx) {
|
|
(void)ctx;
|
|
|
|
if (!sAc) {
|
|
return false;
|
|
}
|
|
|
|
return dvxUpdate(sAc);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// appMain
|
|
// ============================================================
|
|
|
|
int32_t appMain(DxeAppContextT *ctx) {
|
|
sAc = ctx->shellCtx;
|
|
|
|
// Open our own resources
|
|
DvxResHandleT *res = dvxResOpen(ctx->appPath);
|
|
|
|
if (!res) {
|
|
dvxMessageBox(sAc, "Error", "No compiled module found in this application.", 0);
|
|
return 1;
|
|
}
|
|
|
|
// Read app name and update the shell's app record
|
|
uint32_t nameSize = 0;
|
|
char *appName = (char *)dvxResRead(res, "name", &nameSize);
|
|
|
|
if (appName) {
|
|
ShellAppT *app = shellGetApp(ctx->appId);
|
|
|
|
if (app) {
|
|
snprintf(app->name, SHELL_APP_NAME_MAX, "%s", appName);
|
|
}
|
|
|
|
free(appName);
|
|
}
|
|
|
|
// Set help file path if present
|
|
uint32_t helpNameSize = 0;
|
|
char *helpName = (char *)dvxResRead(res, "helpfile", &helpNameSize);
|
|
|
|
if (helpName) {
|
|
snprintf(ctx->helpFile, sizeof(ctx->helpFile), "%s%c%s", ctx->appDir, DVX_PATH_SEP, helpName);
|
|
free(helpName);
|
|
}
|
|
|
|
// Load MODULE resource
|
|
uint32_t modSize = 0;
|
|
uint8_t *modData = (uint8_t *)dvxResRead(res, "MODULE", &modSize);
|
|
|
|
if (!modData) {
|
|
dvxMessageBox(sAc, "Error", "MODULE resource not found.", 0);
|
|
dvxResClose(res);
|
|
return 1;
|
|
}
|
|
|
|
BasModuleT *mod = basModuleDeserialize(modData, (int32_t)modSize);
|
|
free(modData);
|
|
|
|
if (!mod) {
|
|
dvxMessageBox(sAc, "Error", "Failed to deserialize module.", 0);
|
|
dvxResClose(res);
|
|
return 1;
|
|
}
|
|
|
|
// Load optional DEBUG resource
|
|
uint32_t dbgSize = 0;
|
|
uint8_t *dbgData = (uint8_t *)dvxResRead(res, "DEBUG", &dbgSize);
|
|
|
|
if (dbgData) {
|
|
basDebugDeserialize(mod, dbgData, (int32_t)dbgSize);
|
|
free(dbgData);
|
|
}
|
|
|
|
// Create VM
|
|
BasVmT *vm = basVmCreate();
|
|
basVmLoadModule(vm, mod);
|
|
basVmSetPrintCallback(vm, stubPrint, NULL);
|
|
basVmSetInputCallback(vm, stubInput, NULL);
|
|
basVmSetDoEventsCallback(vm, stubDoEvents, NULL);
|
|
|
|
// Set app paths
|
|
snprintf(vm->appPath, DVX_MAX_PATH, "%s", ctx->appDir);
|
|
snprintf(vm->appConfig, DVX_MAX_PATH, "%s", ctx->appDir);
|
|
snprintf(vm->appData, DVX_MAX_PATH, "%s", ctx->appDir);
|
|
|
|
// Set extern call callbacks (required for DECLARE LIBRARY functions)
|
|
BasExternCallbacksT extCb;
|
|
memset(&extCb, 0, sizeof(extCb));
|
|
extCb.resolveExtern = basExternResolve;
|
|
extCb.callExtern = basExternCall;
|
|
basVmSetExternCallbacks(vm, &extCb);
|
|
|
|
// Create form runtime
|
|
BasFormRtT *rt = basFormRtCreate(sAc, vm, mod);
|
|
|
|
// Register .frm source text for lazy loading
|
|
for (int32_t i = 0; i < 256; i++) {
|
|
char resName[16];
|
|
snprintf(resName, sizeof(resName), "FORM%ld", (long)i);
|
|
|
|
uint32_t frmSize = 0;
|
|
char *frmText = (char *)dvxResRead(res, resName, &frmSize);
|
|
|
|
if (!frmText) {
|
|
break;
|
|
}
|
|
|
|
// Extract form name from "Begin Form <name>" line
|
|
char frmName[BAS_MAX_CTRL_NAME] = "";
|
|
const char *p = frmText;
|
|
|
|
while (*p) {
|
|
while (*p == ' ' || *p == '\t') { p++; }
|
|
|
|
if (strncasecmp(p, "Begin Form ", 11) == 0) {
|
|
p += 11;
|
|
|
|
while (*p == ' ' || *p == '\t') { p++; }
|
|
|
|
int32_t ni = 0;
|
|
|
|
while (*p && *p != ' ' && *p != '\t' && *p != '\r' && *p != '\n' && ni < BAS_MAX_CTRL_NAME - 1) {
|
|
frmName[ni++] = *p++;
|
|
}
|
|
|
|
frmName[ni] = '\0';
|
|
break;
|
|
}
|
|
|
|
while (*p && *p != '\n') { p++; }
|
|
|
|
if (*p == '\n') { p++; }
|
|
}
|
|
|
|
if (frmName[0]) {
|
|
basFormRtRegisterFrm(rt, frmName, frmText, (int32_t)frmSize);
|
|
}
|
|
|
|
free(frmText);
|
|
}
|
|
|
|
dvxResClose(res);
|
|
|
|
// Load all cached forms and show the startup form
|
|
basFormRtLoadAllForms(rt, NULL);
|
|
|
|
// Run bytecode + VB-style event loop
|
|
basFormRtRunSimple(rt);
|
|
|
|
// Cleanup
|
|
basFormRtDestroy(rt);
|
|
basVmDestroy(vm);
|
|
basModuleFree(mod);
|
|
|
|
return 0;
|
|
}
|