Dynamic library support added to BASIC.
This commit is contained in:
parent
aa961425c9
commit
f62b89fc02
8 changed files with 801 additions and 10 deletions
|
|
@ -18,6 +18,7 @@
|
||||||
#define BAS_TYPE_BOOLEAN 5 // True (-1) or False (0)
|
#define BAS_TYPE_BOOLEAN 5 // True (-1) or False (0)
|
||||||
#define BAS_TYPE_ARRAY 6 // ref-counted array
|
#define BAS_TYPE_ARRAY 6 // ref-counted array
|
||||||
#define BAS_TYPE_UDT 7 // ref-counted user-defined type
|
#define BAS_TYPE_UDT 7 // ref-counted user-defined type
|
||||||
|
#define BAS_TYPE_OBJECT 8 // opaque host object (form, control, etc.)
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Stack operations
|
// Stack operations
|
||||||
|
|
@ -162,18 +163,33 @@
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// UI / Event (used when form system is active)
|
// 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_LOAD_PROP 0x80 // pop propName, pop ctrlRef, push property value
|
||||||
#define OP_STORE_PROP 0x81 // [uint16 ctrl] [uint16 prop] pop to property
|
#define OP_STORE_PROP 0x81 // pop value, pop propName, pop ctrlRef, set property
|
||||||
#define OP_CALL_METHOD 0x82 // [uint16 ctrl] [uint16 method] [uint8 argc]
|
#define OP_CALL_METHOD 0x82 // [uint8 argc] pop methodName, pop ctrlRef, pop args, push result
|
||||||
#define OP_LOAD_FORM 0x83 // [uint16 formIdx]
|
#define OP_LOAD_FORM 0x83 // pop formName string, push form reference
|
||||||
#define OP_UNLOAD_FORM 0x84 // [uint16 formIdx]
|
#define OP_UNLOAD_FORM 0x84 // pop formRef, unload it
|
||||||
#define OP_SHOW_FORM 0x85 // [uint16 formIdx] [uint8 modal]
|
#define OP_SHOW_FORM 0x85 // [uint8 modal] pop formRef, show it
|
||||||
#define OP_HIDE_FORM 0x86 // [uint16 formIdx]
|
#define OP_HIDE_FORM 0x86 // pop formRef, hide it
|
||||||
#define OP_DO_EVENTS 0x87
|
#define OP_DO_EVENTS 0x87
|
||||||
#define OP_MSGBOX 0x88 // [uint8 flags] message on stack
|
#define OP_MSGBOX 0x88 // [uint8 flags] pop message string, push result
|
||||||
#define OP_INPUTBOX 0x89 // prompt on stack, 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_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
|
// Array / misc
|
||||||
|
|
@ -278,6 +294,18 @@
|
||||||
#define OP_SHELL 0xCB // pop command string, call system(), push return value
|
#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)
|
#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
|
// Halt
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
|
|
@ -492,6 +492,17 @@ static void emitFunctionCall(BasParserT *p, BasSymbolT *sym) {
|
||||||
return;
|
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
|
// baseSlot: functions reserve slot 0 for the return value
|
||||||
uint8_t baseSlot = (sym->kind == SYM_FUNCTION) ? 1 : 0;
|
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) {
|
static void parseDeclare(BasParserT *p) {
|
||||||
// DECLARE SUB name(params)
|
// DECLARE SUB name(params)
|
||||||
// DECLARE FUNCTION name(params) AS type
|
// DECLARE FUNCTION name(params) AS type
|
||||||
|
// DECLARE LIBRARY "name" ... END DECLARE
|
||||||
advance(p); // consume DECLARE
|
advance(p); // consume DECLARE
|
||||||
|
|
||||||
|
// DECLARE LIBRARY block
|
||||||
|
if (checkKeyword(p, "LIBRARY")) {
|
||||||
|
parseDeclareLibrary(p);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
BasSymKindE kind;
|
BasSymKindE kind;
|
||||||
|
|
||||||
if (check(p, TOK_SUB)) {
|
if (check(p, TOK_SUB)) {
|
||||||
|
|
@ -1588,7 +1608,7 @@ static void parseDeclare(BasParserT *p) {
|
||||||
kind = SYM_FUNCTION;
|
kind = SYM_FUNCTION;
|
||||||
advance(p);
|
advance(p);
|
||||||
} else {
|
} else {
|
||||||
error(p, "Expected SUB or FUNCTION after DECLARE");
|
error(p, "Expected SUB, FUNCTION, or LIBRARY after DECLARE");
|
||||||
return;
|
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) {
|
static void parseDef(BasParserT *p) {
|
||||||
// DEF FNname(params) = expression
|
// DEF FNname(params) = expression
|
||||||
advance(p); // consume DEF
|
advance(p); // consume DEF
|
||||||
|
|
|
||||||
|
|
@ -62,8 +62,11 @@ typedef struct {
|
||||||
bool isDefined; // false = forward-declared
|
bool isDefined; // false = forward-declared
|
||||||
bool isArray;
|
bool isArray;
|
||||||
bool isShared;
|
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 udtTypeId; // for variables of BAS_TYPE_UDT: index of TYPE_DEF symbol
|
||||||
int32_t fixedLen; // for STRING * n: fixed length (0 = variable-length)
|
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
|
// For SUB/FUNCTION: parameter info
|
||||||
int32_t paramCount;
|
int32_t paramCount;
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
BasValueT basValCopy(BasValueT v) {
|
||||||
if (v.type == BAS_TYPE_STRING && v.strVal) {
|
if (v.type == BAS_TYPE_STRING && v.strVal) {
|
||||||
basStringRef(v.strVal);
|
basStringRef(v.strVal);
|
||||||
|
|
|
||||||
|
|
@ -130,6 +130,7 @@ struct BasValueTag {
|
||||||
int16_t boolVal; // BAS_TYPE_BOOLEAN (True=-1, False=0)
|
int16_t boolVal; // BAS_TYPE_BOOLEAN (True=-1, False=0)
|
||||||
BasArrayT *arrVal; // BAS_TYPE_ARRAY (ref-counted)
|
BasArrayT *arrVal; // BAS_TYPE_ARRAY (ref-counted)
|
||||||
BasUdtT *udtVal; // BAS_TYPE_UDT (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 basValString(BasStringT *s);
|
||||||
BasValueT basValStringFromC(const char *text);
|
BasValueT basValStringFromC(const char *text);
|
||||||
BasValueT basValBool(bool v);
|
BasValueT basValBool(bool v);
|
||||||
|
BasValueT basValObject(void *obj);
|
||||||
|
|
||||||
// Copy a value (increments string refcount if applicable).
|
// Copy a value (increments string refcount if applicable).
|
||||||
BasValueT basValCopy(BasValueT v);
|
BasValueT basValCopy(BasValueT v);
|
||||||
|
|
|
||||||
|
|
@ -207,6 +207,15 @@ void basVmSetDoEventsCallback(BasVmT *vm, BasDoEventsFnT fn, void *ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// basVmSetCurrentForm
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void basVmSetCurrentForm(BasVmT *vm, void *formRef) {
|
||||||
|
vm->currentForm = formRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// basVmSetInputCallback
|
// 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
|
// basVmStep -- execute one instruction
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -2101,6 +2124,381 @@ BasVmResultE basVmStep(BasVmT *vm) {
|
||||||
vm->running = false;
|
vm->running = false;
|
||||||
return BAS_VM_HALTED;
|
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:
|
default:
|
||||||
runtimeError(vm, 51, "Bad opcode");
|
runtimeError(vm, 51, "Bad opcode");
|
||||||
return BAS_VM_BAD_OPCODE;
|
return BAS_VM_BAD_OPCODE;
|
||||||
|
|
|
||||||
|
|
@ -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.
|
// true to continue execution, false to stop the program.
|
||||||
typedef bool (*BasDoEventsFnT)(void *ctx);
|
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
|
// Call stack frame
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -170,6 +272,19 @@ typedef struct {
|
||||||
void *inputCtx;
|
void *inputCtx;
|
||||||
BasDoEventsFnT doEventsFn;
|
BasDoEventsFnT doEventsFn;
|
||||||
void *doEventsCtx;
|
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;
|
} BasVmT;
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -201,6 +316,15 @@ void basVmSetPrintCallback(BasVmT *vm, BasPrintFnT fn, void *ctx);
|
||||||
void basVmSetInputCallback(BasVmT *vm, BasInputFnT fn, void *ctx);
|
void basVmSetInputCallback(BasVmT *vm, BasInputFnT fn, void *ctx);
|
||||||
void basVmSetDoEventsCallback(BasVmT *vm, BasDoEventsFnT 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).
|
// Push/pop values on the evaluation stack (for host integration).
|
||||||
bool basVmPush(BasVmT *vm, BasValueT val);
|
bool basVmPush(BasVmT *vm, BasValueT val);
|
||||||
bool basVmPop(BasVmT *vm, BasValueT *val);
|
bool basVmPop(BasVmT *vm, BasValueT *val);
|
||||||
|
|
|
||||||
|
|
@ -845,6 +845,64 @@ int main(void) {
|
||||||
printf("\n");
|
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");
|
printf("All tests complete.\n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue