Dynamic library support added to BASIC.

This commit is contained in:
Scott Duensing 2026-03-27 01:01:38 -05:00
parent aa961425c9
commit f62b89fc02
8 changed files with 801 additions and 10 deletions

View file

@ -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
// ============================================================

View file

@ -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

View file

@ -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;

View file

@ -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);

View file

@ -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);

View file

@ -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;

View file

@ -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);

View file

@ -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;
}