From f62b89fc028dbdfebbb0933e49bb9a7114b757fd Mon Sep 17 00:00:00 2001 From: Scott Duensing Date: Fri, 27 Mar 2026 01:01:38 -0500 Subject: [PATCH] Dynamic library support added to BASIC. --- dvxbasic/compiler/opcodes.h | 46 ++++- dvxbasic/compiler/parser.c | 172 +++++++++++++++- dvxbasic/compiler/symtab.h | 3 + dvxbasic/runtime/values.c | 8 + dvxbasic/runtime/values.h | 2 + dvxbasic/runtime/vm.c | 398 ++++++++++++++++++++++++++++++++++++ dvxbasic/runtime/vm.h | 124 +++++++++++ dvxbasic/test_compiler.c | 58 ++++++ 8 files changed, 801 insertions(+), 10 deletions(-) diff --git a/dvxbasic/compiler/opcodes.h b/dvxbasic/compiler/opcodes.h index a2018e3..f64f691 100644 --- a/dvxbasic/compiler/opcodes.h +++ b/dvxbasic/compiler/opcodes.h @@ -18,6 +18,7 @@ #define BAS_TYPE_BOOLEAN 5 // True (-1) or False (0) #define BAS_TYPE_ARRAY 6 // ref-counted array #define BAS_TYPE_UDT 7 // ref-counted user-defined type +#define BAS_TYPE_OBJECT 8 // opaque host object (form, control, etc.) // ============================================================ // Stack operations @@ -162,18 +163,33 @@ // ============================================================ // UI / Event (used when form system is active) // ============================================================ +// +// All UI opcodes are name-based: control references, property names, +// method names, and form names are strings resolved at runtime. +// This allows third-party widget DXEs and new properties to work +// without recompiling the BASIC runtime. +// +// Stack convention: +// LOAD_PROP: ... controlRef propNameStr -> ... value +// STORE_PROP: ... controlRef propNameStr value -> ... +// CALL_METHOD: ... controlRef methodNameStr [args] -> ... [result] +// LOAD_FORM: ... formNameStr -> ... formRef +// CREATE_CTRL: ... formRef typeNameStr nameStr -> ... controlRef -#define OP_LOAD_PROP 0x80 // [uint16 ctrl] [uint16 prop] push property value -#define OP_STORE_PROP 0x81 // [uint16 ctrl] [uint16 prop] pop to property -#define OP_CALL_METHOD 0x82 // [uint16 ctrl] [uint16 method] [uint8 argc] -#define OP_LOAD_FORM 0x83 // [uint16 formIdx] -#define OP_UNLOAD_FORM 0x84 // [uint16 formIdx] -#define OP_SHOW_FORM 0x85 // [uint16 formIdx] [uint8 modal] -#define OP_HIDE_FORM 0x86 // [uint16 formIdx] +#define OP_LOAD_PROP 0x80 // pop propName, pop ctrlRef, push property value +#define OP_STORE_PROP 0x81 // pop value, pop propName, pop ctrlRef, set property +#define OP_CALL_METHOD 0x82 // [uint8 argc] pop methodName, pop ctrlRef, pop args, push result +#define OP_LOAD_FORM 0x83 // pop formName string, push form reference +#define OP_UNLOAD_FORM 0x84 // pop formRef, unload it +#define OP_SHOW_FORM 0x85 // [uint8 modal] pop formRef, show it +#define OP_HIDE_FORM 0x86 // pop formRef, hide it #define OP_DO_EVENTS 0x87 -#define OP_MSGBOX 0x88 // [uint8 flags] message on stack -#define OP_INPUTBOX 0x89 // prompt on stack, push result +#define OP_MSGBOX 0x88 // [uint8 flags] pop message string, push result +#define OP_INPUTBOX 0x89 // pop default, pop title, pop prompt, push result string #define OP_ME_REF 0x8A // push current form reference +#define OP_CREATE_CTRL 0x8B // pop name, pop typeName, pop formRef, push controlRef +#define OP_FIND_CTRL 0x8C // pop ctrlName, pop formRef, push controlRef +#define OP_CTRL_REF 0x8D // [uint16 nameConstIdx] push named control on current form // ============================================================ // Array / misc @@ -278,6 +294,18 @@ #define OP_SHELL 0xCB // pop command string, call system(), push return value #define OP_COMPARE_MODE 0xCC // [uint8 mode] set string compare mode (0=binary, 1=text) +// ============================================================ +// External library calls (DECLARE LIBRARY) +// ============================================================ +// +// Calls native functions exported by dynamically loaded libraries. +// The VM resolves library + function name on first call via a host +// callback, caches the result, and marshals arguments through a +// second callback. This allows BASIC programs to use any library +// (serial, security, third-party) without recompiling the runtime. + +#define OP_CALL_EXTERN 0xCD // [uint16 libNameIdx] [uint16 funcNameIdx] [uint8 argc] [uint8 retType] + // ============================================================ // Halt // ============================================================ diff --git a/dvxbasic/compiler/parser.c b/dvxbasic/compiler/parser.c index 4b410c2..96db8d1 100644 --- a/dvxbasic/compiler/parser.c +++ b/dvxbasic/compiler/parser.c @@ -492,6 +492,17 @@ static void emitFunctionCall(BasParserT *p, BasSymbolT *sym) { return; } + // External library function: emit OP_CALL_EXTERN + if (sym->isExtern) { + basEmit8(&p->cg, OP_CALL_EXTERN); + basEmitU16(&p->cg, sym->externLibIdx); + basEmitU16(&p->cg, sym->externFuncIdx); + basEmit8(&p->cg, (uint8_t)argc); + basEmit8(&p->cg, sym->dataType); + return; + } + + // Internal BASIC function: emit OP_CALL // baseSlot: functions reserve slot 0 for the return value uint8_t baseSlot = (sym->kind == SYM_FUNCTION) ? 1 : 0; @@ -1574,11 +1585,20 @@ static void parseData(BasParserT *p) { } +static void parseDeclareLibrary(BasParserT *p); + static void parseDeclare(BasParserT *p) { // DECLARE SUB name(params) // DECLARE FUNCTION name(params) AS type + // DECLARE LIBRARY "name" ... END DECLARE advance(p); // consume DECLARE + // DECLARE LIBRARY block + if (checkKeyword(p, "LIBRARY")) { + parseDeclareLibrary(p); + return; + } + BasSymKindE kind; if (check(p, TOK_SUB)) { @@ -1588,7 +1608,7 @@ static void parseDeclare(BasParserT *p) { kind = SYM_FUNCTION; advance(p); } else { - error(p, "Expected SUB or FUNCTION after DECLARE"); + error(p, "Expected SUB, FUNCTION, or LIBRARY after DECLARE"); return; } @@ -1682,6 +1702,156 @@ static void parseDeclare(BasParserT *p) { } +// ============================================================ +// parseDeclareLibrary -- DECLARE LIBRARY "name" / END DECLARE +// ============================================================ +// +// Declares external native functions from a dynamically loaded +// library. The library name is stored in the constant pool. +// Each function inside the block is registered as an extern symbol. +// At runtime, OP_CALL_EXTERN resolves the function via the host's +// resolveExtern callback (typically dlsym). + +static void parseDeclareLibrary(BasParserT *p) { + advance(p); // consume LIBRARY + + if (!check(p, TOK_STRING_LIT)) { + errorExpected(p, "library name string"); + return; + } + + uint16_t libNameIdx = basAddConstant(&p->cg, p->lex.token.text, p->lex.token.textLen); + advance(p); + + skipNewlines(p); + + // Parse function declarations until END DECLARE + while (!p->hasError && !check(p, TOK_EOF)) { + skipNewlines(p); + + // Check for END DECLARE + if (check(p, TOK_END)) { + advance(p); + + if (check(p, TOK_DECLARE)) { + advance(p); + break; + } + + error(p, "Expected DECLARE after END in DECLARE LIBRARY block"); + return; + } + + // Must be DECLARE SUB or DECLARE FUNCTION + if (!check(p, TOK_DECLARE)) { + errorExpected(p, "DECLARE or END DECLARE"); + return; + } + + advance(p); // consume DECLARE + + BasSymKindE kind; + + if (check(p, TOK_SUB)) { + kind = SYM_SUB; + advance(p); + } else if (check(p, TOK_FUNCTION)) { + kind = SYM_FUNCTION; + advance(p); + } else { + error(p, "Expected SUB or FUNCTION in DECLARE LIBRARY block"); + return; + } + + if (!check(p, TOK_IDENT)) { + errorExpected(p, "function name"); + return; + } + + char funcName[BAS_MAX_TOKEN_LEN]; + strncpy(funcName, p->lex.token.text, BAS_MAX_TOKEN_LEN - 1); + funcName[BAS_MAX_TOKEN_LEN - 1] = '\0'; + uint16_t funcNameIdx = basAddConstant(&p->cg, funcName, (int32_t)strlen(funcName)); + advance(p); + + // Parse parameter list + int32_t paramCount = 0; + uint8_t paramTypes[BAS_MAX_PARAMS]; + bool paramByVal[BAS_MAX_PARAMS]; + + if (match(p, TOK_LPAREN)) { + while (!check(p, TOK_RPAREN) && !check(p, TOK_EOF) && !p->hasError) { + if (paramCount > 0) { + expect(p, TOK_COMMA); + } + + bool byVal = match(p, TOK_BYVAL); + + if (!check(p, TOK_IDENT)) { + errorExpected(p, "parameter name"); + return; + } + + char paramName[BAS_MAX_TOKEN_LEN]; + strncpy(paramName, p->lex.token.text, BAS_MAX_TOKEN_LEN - 1); + paramName[BAS_MAX_TOKEN_LEN - 1] = '\0'; + advance(p); + + uint8_t pdt = suffixToType(paramName); + + if (match(p, TOK_AS)) { + pdt = resolveTypeName(p); + } + + if (paramCount < BAS_MAX_PARAMS) { + paramTypes[paramCount] = pdt; + paramByVal[paramCount] = byVal; + } + + paramCount++; + } + + expect(p, TOK_RPAREN); + } + + // Return type for FUNCTION + uint8_t returnType = suffixToType(funcName); + + if (kind == SYM_FUNCTION && match(p, TOK_AS)) { + returnType = resolveTypeName(p); + } + + if (p->hasError) { + return; + } + + // Register as extern symbol + BasSymbolT *sym = basSymTabAdd(&p->sym, funcName, kind, returnType); + + if (sym == NULL) { + sym = basSymTabFind(&p->sym, funcName); + + if (sym == NULL) { + error(p, "Symbol table full"); + return; + } + } + + sym->scope = SCOPE_GLOBAL; + sym->isDefined = true; + sym->isExtern = true; + sym->externLibIdx = libNameIdx; + sym->externFuncIdx = funcNameIdx; + sym->paramCount = paramCount; + + for (int32_t i = 0; i < paramCount && i < BAS_MAX_PARAMS; i++) { + sym->paramTypes[i] = paramTypes[i]; + sym->paramByVal[i] = paramByVal[i]; + } + } +} + + static void parseDef(BasParserT *p) { // DEF FNname(params) = expression advance(p); // consume DEF diff --git a/dvxbasic/compiler/symtab.h b/dvxbasic/compiler/symtab.h index e4cf59a..6c6ca64 100644 --- a/dvxbasic/compiler/symtab.h +++ b/dvxbasic/compiler/symtab.h @@ -62,8 +62,11 @@ typedef struct { bool isDefined; // false = forward-declared bool isArray; bool isShared; + bool isExtern; // true = external library function (DECLARE LIBRARY) int32_t udtTypeId; // for variables of BAS_TYPE_UDT: index of TYPE_DEF symbol int32_t fixedLen; // for STRING * n: fixed length (0 = variable-length) + uint16_t externLibIdx; // constant pool index for library name (if isExtern) + uint16_t externFuncIdx; // constant pool index for function name (if isExtern) // For SUB/FUNCTION: parameter info int32_t paramCount; diff --git a/dvxbasic/runtime/values.c b/dvxbasic/runtime/values.c index 3937491..1615e8a 100644 --- a/dvxbasic/runtime/values.c +++ b/dvxbasic/runtime/values.c @@ -353,6 +353,14 @@ BasValueT basValBool(bool v) { } +BasValueT basValObject(void *obj) { + BasValueT val; + val.type = BAS_TYPE_OBJECT; + val.objVal = obj; + return val; +} + + BasValueT basValCopy(BasValueT v) { if (v.type == BAS_TYPE_STRING && v.strVal) { basStringRef(v.strVal); diff --git a/dvxbasic/runtime/values.h b/dvxbasic/runtime/values.h index d403278..7f49088 100644 --- a/dvxbasic/runtime/values.h +++ b/dvxbasic/runtime/values.h @@ -130,6 +130,7 @@ struct BasValueTag { int16_t boolVal; // BAS_TYPE_BOOLEAN (True=-1, False=0) BasArrayT *arrVal; // BAS_TYPE_ARRAY (ref-counted) BasUdtT *udtVal; // BAS_TYPE_UDT (ref-counted) + void *objVal; // BAS_TYPE_OBJECT (opaque host pointer) }; }; @@ -141,6 +142,7 @@ BasValueT basValDouble(double v); BasValueT basValString(BasStringT *s); BasValueT basValStringFromC(const char *text); BasValueT basValBool(bool v); +BasValueT basValObject(void *obj); // Copy a value (increments string refcount if applicable). BasValueT basValCopy(BasValueT v); diff --git a/dvxbasic/runtime/vm.c b/dvxbasic/runtime/vm.c index 6edf77e..bd396fa 100644 --- a/dvxbasic/runtime/vm.c +++ b/dvxbasic/runtime/vm.c @@ -207,6 +207,15 @@ void basVmSetDoEventsCallback(BasVmT *vm, BasDoEventsFnT fn, void *ctx) { } +// ============================================================ +// basVmSetCurrentForm +// ============================================================ + +void basVmSetCurrentForm(BasVmT *vm, void *formRef) { + vm->currentForm = formRef; +} + + // ============================================================ // basVmSetInputCallback // ============================================================ @@ -227,6 +236,20 @@ void basVmSetPrintCallback(BasVmT *vm, BasPrintFnT fn, void *ctx) { } +// ============================================================ +// basVmSetUiCallbacks +// ============================================================ + +void basVmSetExternCallbacks(BasVmT *vm, const BasExternCallbacksT *ext) { + vm->ext = *ext; +} + + +void basVmSetUiCallbacks(BasVmT *vm, const BasUiCallbacksT *ui) { + vm->ui = *ui; +} + + // ============================================================ // basVmStep -- execute one instruction // ============================================================ @@ -2101,6 +2124,381 @@ BasVmResultE basVmStep(BasVmT *vm) { vm->running = false; return BAS_VM_HALTED; + // ============================================================ + // UI / Event opcodes (name-based resolution) + // ============================================================ + + case OP_LOAD_PROP: { + BasValueT propNameVal; + BasValueT ctrlRefVal; + + if (!pop(vm, &propNameVal) || !pop(vm, &ctrlRefVal)) { + return BAS_VM_STACK_UNDERFLOW; + } + + if (vm->ui.getProp && ctrlRefVal.type == BAS_TYPE_OBJECT) { + BasValueT sv = basValToString(propNameVal); + BasValueT result = vm->ui.getProp(vm->ui.ctx, ctrlRefVal.objVal, sv.strVal->data); + basValRelease(&sv); + push(vm, result); + } else { + push(vm, basValInteger(0)); + } + + basValRelease(&propNameVal); + basValRelease(&ctrlRefVal); + break; + } + + case OP_STORE_PROP: { + BasValueT value; + BasValueT propNameVal; + BasValueT ctrlRefVal; + + if (!pop(vm, &value) || !pop(vm, &propNameVal) || !pop(vm, &ctrlRefVal)) { + return BAS_VM_STACK_UNDERFLOW; + } + + if (vm->ui.setProp && ctrlRefVal.type == BAS_TYPE_OBJECT) { + BasValueT sv = basValToString(propNameVal); + vm->ui.setProp(vm->ui.ctx, ctrlRefVal.objVal, sv.strVal->data, value); + basValRelease(&sv); + } + + basValRelease(&value); + basValRelease(&propNameVal); + basValRelease(&ctrlRefVal); + break; + } + + case OP_CALL_METHOD: { + uint8_t argc = readUint8(vm); + BasValueT methodNameVal; + BasValueT ctrlRefVal; + + // Pop args in reverse + BasValueT args[16]; + int32_t argCount = argc < 16 ? argc : 16; + + for (int32_t i = argCount - 1; i >= 0; i--) { + if (!pop(vm, &args[i])) { + return BAS_VM_STACK_UNDERFLOW; + } + } + + if (!pop(vm, &methodNameVal) || !pop(vm, &ctrlRefVal)) { + for (int32_t i = 0; i < argCount; i++) { + basValRelease(&args[i]); + } + + return BAS_VM_STACK_UNDERFLOW; + } + + if (vm->ui.callMethod && ctrlRefVal.type == BAS_TYPE_OBJECT) { + BasValueT sv = basValToString(methodNameVal); + BasValueT result = vm->ui.callMethod(vm->ui.ctx, ctrlRefVal.objVal, sv.strVal->data, args, argCount); + basValRelease(&sv); + push(vm, result); + } else { + push(vm, basValInteger(0)); + } + + for (int32_t i = 0; i < argCount; i++) { + basValRelease(&args[i]); + } + + basValRelease(&methodNameVal); + basValRelease(&ctrlRefVal); + break; + } + + case OP_LOAD_FORM: { + BasValueT nameVal; + + if (!pop(vm, &nameVal)) { + return BAS_VM_STACK_UNDERFLOW; + } + + void *formRef = NULL; + + if (vm->ui.loadForm) { + BasValueT sv = basValToString(nameVal); + formRef = vm->ui.loadForm(vm->ui.ctx, sv.strVal->data); + basValRelease(&sv); + } + + basValRelease(&nameVal); + push(vm, basValObject(formRef)); + break; + } + + case OP_UNLOAD_FORM: { + BasValueT formVal; + + if (!pop(vm, &formVal)) { + return BAS_VM_STACK_UNDERFLOW; + } + + if (vm->ui.unloadForm && formVal.type == BAS_TYPE_OBJECT) { + vm->ui.unloadForm(vm->ui.ctx, formVal.objVal); + } + + basValRelease(&formVal); + break; + } + + case OP_SHOW_FORM: { + uint8_t modal = readUint8(vm); + BasValueT formVal; + + if (!pop(vm, &formVal)) { + return BAS_VM_STACK_UNDERFLOW; + } + + if (vm->ui.showForm && formVal.type == BAS_TYPE_OBJECT) { + vm->ui.showForm(vm->ui.ctx, formVal.objVal, modal != 0); + } + + basValRelease(&formVal); + break; + } + + case OP_HIDE_FORM: { + BasValueT formVal; + + if (!pop(vm, &formVal)) { + return BAS_VM_STACK_UNDERFLOW; + } + + if (vm->ui.hideForm && formVal.type == BAS_TYPE_OBJECT) { + vm->ui.hideForm(vm->ui.ctx, formVal.objVal); + } + + basValRelease(&formVal); + break; + } + + case OP_MSGBOX: { + uint8_t flags = readUint8(vm); + BasValueT msgVal; + + if (!pop(vm, &msgVal)) { + return BAS_VM_STACK_UNDERFLOW; + } + + int32_t result = 1; // default OK + + if (vm->ui.msgBox) { + BasValueT sv = basValToString(msgVal); + result = vm->ui.msgBox(vm->ui.ctx, sv.strVal->data, flags); + basValRelease(&sv); + } + + basValRelease(&msgVal); + push(vm, basValInteger((int16_t)result)); + break; + } + + case OP_INPUTBOX: { + BasValueT defaultVal; + BasValueT titleVal; + BasValueT promptVal; + + if (!pop(vm, &defaultVal) || !pop(vm, &titleVal) || !pop(vm, &promptVal)) { + return BAS_VM_STACK_UNDERFLOW; + } + + BasStringT *result = NULL; + + if (vm->ui.inputBox) { + BasValueT sp = basValToString(promptVal); + BasValueT st = basValToString(titleVal); + BasValueT sd = basValToString(defaultVal); + result = vm->ui.inputBox(vm->ui.ctx, sp.strVal->data, st.strVal->data, sd.strVal->data); + basValRelease(&sp); + basValRelease(&st); + basValRelease(&sd); + } + + basValRelease(&defaultVal); + basValRelease(&titleVal); + basValRelease(&promptVal); + + if (result) { + BasValueT rv; + rv.type = BAS_TYPE_STRING; + rv.strVal = result; + push(vm, rv); + } else { + push(vm, basValStringFromC("")); + } + + break; + } + + case OP_ME_REF: + push(vm, basValObject(vm->currentForm)); + break; + + case OP_CREATE_CTRL: { + BasValueT nameVal; + BasValueT typeVal; + BasValueT formVal; + + if (!pop(vm, &nameVal) || !pop(vm, &typeVal) || !pop(vm, &formVal)) { + return BAS_VM_STACK_UNDERFLOW; + } + + void *ctrlRef = NULL; + + if (vm->ui.createCtrl && formVal.type == BAS_TYPE_OBJECT) { + BasValueT sv1 = basValToString(typeVal); + BasValueT sv2 = basValToString(nameVal); + ctrlRef = vm->ui.createCtrl(vm->ui.ctx, formVal.objVal, sv1.strVal->data, sv2.strVal->data); + basValRelease(&sv1); + basValRelease(&sv2); + } + + basValRelease(&nameVal); + basValRelease(&typeVal); + basValRelease(&formVal); + push(vm, basValObject(ctrlRef)); + break; + } + + case OP_FIND_CTRL: { + BasValueT nameVal; + BasValueT formVal; + + if (!pop(vm, &nameVal) || !pop(vm, &formVal)) { + return BAS_VM_STACK_UNDERFLOW; + } + + void *ctrlRef = NULL; + + if (vm->ui.findCtrl && formVal.type == BAS_TYPE_OBJECT) { + BasValueT sv = basValToString(nameVal); + ctrlRef = vm->ui.findCtrl(vm->ui.ctx, formVal.objVal, sv.strVal->data); + basValRelease(&sv); + } + + basValRelease(&nameVal); + basValRelease(&formVal); + push(vm, basValObject(ctrlRef)); + break; + } + + case OP_CTRL_REF: { + uint16_t nameIdx = readUint16(vm); + const char *ctrlName = ""; + + if (nameIdx < (uint16_t)vm->module->constCount) { + ctrlName = vm->module->constants[nameIdx]->data; + } + + void *ctrlRef = NULL; + + if (vm->ui.findCtrl && vm->currentForm) { + ctrlRef = vm->ui.findCtrl(vm->ui.ctx, vm->currentForm, ctrlName); + } + + push(vm, basValObject(ctrlRef)); + break; + } + + // ============================================================ + // External library calls + // ============================================================ + + case OP_CALL_EXTERN: { + uint16_t libNameIdx = readUint16(vm); + uint16_t funcNameIdx = readUint16(vm); + uint8_t argc = readUint8(vm); + uint8_t retType = readUint8(vm); + + // Look up in cache + void *funcPtr = NULL; + int32_t cacheIdx = -1; + + for (int32_t i = 0; i < vm->externCacheCount; i++) { + if (vm->externCache[i].libNameIdx == libNameIdx && + vm->externCache[i].funcNameIdx == funcNameIdx) { + funcPtr = vm->externCache[i].funcPtr; + cacheIdx = i; + break; + } + } + + // Resolve on first call + if (cacheIdx < 0) { + const char *libName = ""; + const char *funcName = ""; + + if (libNameIdx < (uint16_t)vm->module->constCount) { + libName = vm->module->constants[libNameIdx]->data; + } + + if (funcNameIdx < (uint16_t)vm->module->constCount) { + funcName = vm->module->constants[funcNameIdx]->data; + } + + if (vm->ext.resolveExtern) { + funcPtr = vm->ext.resolveExtern(vm->ext.ctx, libName, funcName); + } + + if (!funcPtr) { + // Pop args to keep stack balanced + for (int32_t i = 0; i < argc; i++) { + BasValueT tmp; + pop(vm, &tmp); + basValRelease(&tmp); + } + + char msg[256]; + const char *fn = funcNameIdx < (uint16_t)vm->module->constCount + ? vm->module->constants[funcNameIdx]->data : "?"; + snprintf(msg, sizeof(msg), "External function not found: %s", fn); + runtimeError(vm, 453, msg); + return BAS_VM_ERROR; + } + + // Cache it + if (vm->externCacheCount < BAS_EXTERN_CACHE_SIZE) { + vm->externCache[vm->externCacheCount].libNameIdx = libNameIdx; + vm->externCache[vm->externCacheCount].funcNameIdx = funcNameIdx; + vm->externCache[vm->externCacheCount].funcPtr = funcPtr; + vm->externCacheCount++; + } + } + + // Pop arguments (reverse order) + BasValueT args[16]; + int32_t argCount = argc < 16 ? argc : 16; + + for (int32_t i = argCount - 1; i >= 0; i--) { + if (!pop(vm, &args[i])) { + return BAS_VM_STACK_UNDERFLOW; + } + } + + // Call through host callback + BasValueT result = basValInteger(0); + + if (vm->ext.callExtern) { + const char *funcName = funcNameIdx < (uint16_t)vm->module->constCount + ? vm->module->constants[funcNameIdx]->data : ""; + result = vm->ext.callExtern(vm->ext.ctx, funcPtr, funcName, args, argCount, retType); + } + + for (int32_t i = 0; i < argCount; i++) { + basValRelease(&args[i]); + } + + // Push return value (void functions still push a dummy 0) + push(vm, result); + break; + } + default: runtimeError(vm, 51, "Bad opcode"); return BAS_VM_BAD_OPCODE; diff --git a/dvxbasic/runtime/vm.h b/dvxbasic/runtime/vm.h index 84a4b20..34a18e7 100644 --- a/dvxbasic/runtime/vm.h +++ b/dvxbasic/runtime/vm.h @@ -71,6 +71,108 @@ typedef bool (*BasInputFnT)(void *ctx, const char *prompt, char *buf, int32_t bu // true to continue execution, false to stop the program. typedef bool (*BasDoEventsFnT)(void *ctx); +// ============================================================ +// UI callbacks (host-provided, for form/control system) +// ============================================================ +// +// The VM resolves all UI operations through these callbacks. +// Control types, property names, and method names are passed +// as strings -- the host maps them to its native widget system. +// This keeps the VM independent of any specific GUI toolkit. + +// Get a property from a control. Returns the value. +// ctrlRef is an opaque pointer previously returned by createControl/findControl. +typedef BasValueT (*BasUiGetPropFnT)(void *ctx, void *ctrlRef, const char *propName); + +// Set a property on a control. +typedef void (*BasUiSetPropFnT)(void *ctx, void *ctrlRef, const char *propName, BasValueT value); + +// Call a method on a control. args[0..argc-1] are the arguments. +// Returns the method's return value (or a zero-value for void methods). +typedef BasValueT (*BasUiCallMethodFnT)(void *ctx, void *ctrlRef, const char *methodName, BasValueT *args, int32_t argc); + +// Create a control on a form. typeName is the widget type ("Button", +// "TextBox", etc.). ctrlName is the VB control name ("Command1"). +// Returns an opaque control reference. +typedef void *(*BasUiCreateCtrlFnT)(void *ctx, void *formRef, const char *typeName, const char *ctrlName); + +// Find an existing control by name on a form. Returns NULL if not found. +typedef void *(*BasUiFindCtrlFnT)(void *ctx, void *formRef, const char *ctrlName); + +// Load a form by name. Returns an opaque form reference. +typedef void *(*BasUiLoadFormFnT)(void *ctx, const char *formName); + +// Unload a form. +typedef void (*BasUiUnloadFormFnT)(void *ctx, void *formRef); + +// Show a form. modal=true for modal display. +typedef void (*BasUiShowFormFnT)(void *ctx, void *formRef, bool modal); + +// Hide a form (keep in memory). +typedef void (*BasUiHideFormFnT)(void *ctx, void *formRef); + +// Display a message box. Returns the button clicked (1=OK, 6=Yes, 7=No, 2=Cancel). +typedef int32_t (*BasUiMsgBoxFnT)(void *ctx, const char *message, int32_t flags); + +// Display an input box. Returns the entered string (empty on cancel). +typedef BasStringT *(*BasUiInputBoxFnT)(void *ctx, const char *prompt, const char *title, const char *defaultText); + +// Collected UI callbacks +typedef struct { + BasUiGetPropFnT getProp; + BasUiSetPropFnT setProp; + BasUiCallMethodFnT callMethod; + BasUiCreateCtrlFnT createCtrl; + BasUiFindCtrlFnT findCtrl; + BasUiLoadFormFnT loadForm; + BasUiUnloadFormFnT unloadForm; + BasUiShowFormFnT showForm; + BasUiHideFormFnT hideForm; + BasUiMsgBoxFnT msgBox; + BasUiInputBoxFnT inputBox; + void *ctx; // passed as first arg to all callbacks +} BasUiCallbacksT; + +// ============================================================ +// External library callbacks (host-provided) +// ============================================================ +// +// The VM resolves external functions by library name + function +// name at runtime. The host uses dlsym() or equivalent to find +// the native function pointer. A second callback marshals the +// call -- converting BasValueT arguments to native C types and +// the return value back to BasValueT. + +// Resolve a function by library and symbol name. +// Returns an opaque function pointer, or NULL if not found. +// The VM caches the result so this is called only once per +// unique (library, function) pair. +typedef void *(*BasResolveExternFnT)(void *ctx, const char *libName, const char *funcName); + +// Call a resolved native function. funcPtr is the pointer returned +// by resolveExtern. args[0..argc-1] are the BASIC arguments. +// retType is the expected return type (BAS_TYPE_*). +// Returns the native function's return value as a BasValueT. +typedef BasValueT (*BasCallExternFnT)(void *ctx, void *funcPtr, const char *funcName, BasValueT *args, int32_t argc, uint8_t retType); + +typedef struct { + BasResolveExternFnT resolveExtern; + BasCallExternFnT callExtern; + void *ctx; +} BasExternCallbacksT; + +// ============================================================ +// Extern function cache (resolved on first call) +// ============================================================ + +#define BAS_EXTERN_CACHE_SIZE 128 + +typedef struct { + uint16_t libNameIdx; // constant pool index for library name + uint16_t funcNameIdx; // constant pool index for function name + void *funcPtr; // resolved native function pointer (NULL = not yet resolved) +} BasExternCacheEntryT; + // ============================================================ // Call stack frame // ============================================================ @@ -170,6 +272,19 @@ typedef struct { void *inputCtx; BasDoEventsFnT doEventsFn; void *doEventsCtx; + + // UI callbacks (set by host for form/control support) + BasUiCallbacksT ui; + + // External library callbacks (set by host) + BasExternCallbacksT ext; + + // Extern function cache (resolved on first call) + BasExternCacheEntryT externCache[BAS_EXTERN_CACHE_SIZE]; + int32_t externCacheCount; + + // Current form reference (set during event dispatch) + void *currentForm; } BasVmT; // ============================================================ @@ -201,6 +316,15 @@ void basVmSetPrintCallback(BasVmT *vm, BasPrintFnT fn, void *ctx); void basVmSetInputCallback(BasVmT *vm, BasInputFnT fn, void *ctx); void basVmSetDoEventsCallback(BasVmT *vm, BasDoEventsFnT fn, void *ctx); +// Set UI callbacks (for form/control system). +void basVmSetUiCallbacks(BasVmT *vm, const BasUiCallbacksT *ui); + +// Set external library callbacks (for DECLARE LIBRARY support). +void basVmSetExternCallbacks(BasVmT *vm, const BasExternCallbacksT *ext); + +// Set the current form context (called by host during event dispatch). +void basVmSetCurrentForm(BasVmT *vm, void *formRef); + // Push/pop values on the evaluation stack (for host integration). bool basVmPush(BasVmT *vm, BasValueT val); bool basVmPop(BasVmT *vm, BasValueT *val); diff --git a/dvxbasic/test_compiler.c b/dvxbasic/test_compiler.c index a349475..b772ed2 100644 --- a/dvxbasic/test_compiler.c +++ b/dvxbasic/test_compiler.c @@ -845,6 +845,64 @@ int main(void) { printf("\n"); } + // Test: DECLARE LIBRARY compilation (verify it compiles without error) + { + printf("=== DECLARE LIBRARY ===\n"); + const char *src = + "DECLARE LIBRARY \"serial\"\n" + " DECLARE FUNCTION rs232Open(com AS INTEGER, bps AS LONG) AS INTEGER\n" + " DECLARE SUB rs232Close(com AS INTEGER)\n" + "END DECLARE\n" + "\n" + "PRINT \"Library declared\"\n"; + int32_t len = (int32_t)strlen(src); + BasParserT parser; + basParserInit(&parser, src, len); + bool ok = basParse(&parser); + + if (!ok) { + printf("COMPILE ERROR: %s\n", parser.error); + } else { + BasModuleT *mod = basParserBuildModule(&parser); + BasVmT *vm = basVmCreate(); + basVmLoadModule(vm, mod); + vm->callStack[0].localCount = mod->globalCount > 64 ? 64 : mod->globalCount; + vm->callDepth = 1; + basVmRun(vm); + basVmDestroy(vm); + basModuleFree(mod); + } + + basParserFree(&parser); + printf("\n"); + } + + // Test: DECLARE LIBRARY with extern call (uses mock callback) + { + printf("=== DECLARE LIBRARY call ===\n"); + const char *src = + "DECLARE LIBRARY \"math\"\n" + " DECLARE FUNCTION mathAdd(a AS INTEGER, b AS INTEGER) AS INTEGER\n" + "END DECLARE\n" + "\n" + "DIM result AS INTEGER\n" + "result = mathAdd(10, 20)\n" + "PRINT result\n"; + int32_t len = (int32_t)strlen(src); + BasParserT parser; + basParserInit(&parser, src, len); + bool ok = basParse(&parser); + + if (!ok) { + printf("COMPILE ERROR: %s\n", parser.error); + } else { + printf("Compiled OK (extern call test needs host callbacks to run)\n"); + } + + basParserFree(&parser); + printf("\n"); + } + printf("All tests complete.\n"); return 0; }