DVX_GUI/apps/dvxbasic/test_compiler.c

2903 lines
80 KiB
C

// test_compiler.c -- End-to-end test: source -> compiler -> VM -> output
//
// Build: make -C dvxbasic tests
#include "compiler/parser.h"
#include "runtime/vm.h"
#include "runtime/values.h"
#include <stdio.h>
#include <string.h>
static void runProgram(const char *name, const char *source) {
printf("=== %s ===\n", name);
int32_t len = (int32_t)strlen(source);
BasParserT parser;
basParserInit(&parser, source, len);
if (!basParse(&parser)) {
printf("COMPILE ERROR: %s\n\n", parser.error);
basParserFree(&parser);
return;
}
BasModuleT *mod = basParserBuildModule(&parser);
basParserFree(&parser);
if (!mod) {
printf("MODULE BUILD FAILED\n\n");
return;
}
BasVmT *vm = basVmCreate();
basVmLoadModule(vm, mod);
// Module-level code uses callStack[0] as implicit main frame
vm->callStack[0].localCount = mod->globalCount > 64 ? 64 : mod->globalCount;
vm->callDepth = 1;
BasVmResultE result = basVmRun(vm);
if (result != BAS_VM_HALTED && result != BAS_VM_OK) {
printf("[VM error %d: %s]\n", result, basVmGetError(vm));
}
basVmDestroy(vm);
basModuleFree(mod);
printf("\n");
}
int main(void) {
printf("DVX BASIC Compiler Tests\n");
printf("========================\n\n");
basStringSystemInit();
// Test 1: Hello World
runProgram("Hello World",
"PRINT \"Hello, World!\"\n"
);
// Test 2: Arithmetic
runProgram("Arithmetic",
"PRINT 2 + 3 * 4\n"
"PRINT 10 \\ 3\n"
"PRINT 10 MOD 3\n"
"PRINT 2 ^ 8\n"
);
// Test 3: String operations
runProgram("String Ops",
"DIM s AS STRING\n"
"s = \"Hello, BASIC!\"\n"
"PRINT s\n"
"PRINT LEN(s)\n"
"PRINT LEFT$(s, 5)\n"
"PRINT RIGHT$(s, 6)\n"
"PRINT MID$(s, 8, 5)\n"
"PRINT UCASE$(s)\n"
);
// Test 4: IF/THEN/ELSE
runProgram("IF/THEN/ELSE",
"DIM x AS INTEGER\n"
"x = 42\n"
"IF x > 100 THEN\n"
" PRINT \"big\"\n"
"ELSEIF x > 10 THEN\n"
" PRINT \"medium\"\n"
"ELSE\n"
" PRINT \"small\"\n"
"END IF\n"
);
// Test 5: FOR loop
runProgram("FOR Loop",
"DIM i AS INTEGER\n"
"FOR i = 1 TO 10\n"
" PRINT i;\n"
"NEXT i\n"
"PRINT\n"
);
// Test 6: DO/WHILE loop
runProgram("DO/WHILE Loop",
"DIM n AS INTEGER\n"
"n = 1\n"
"DO WHILE n <= 5\n"
" PRINT n;\n"
" n = n + 1\n"
"LOOP\n"
"PRINT\n"
);
// Test 7: SUB and FUNCTION
runProgram("SUB and FUNCTION",
"DECLARE SUB Greet(name AS STRING)\n"
"DECLARE FUNCTION Square(x AS INTEGER) AS INTEGER\n"
"\n"
"CALL Greet(\"World\")\n"
"PRINT Square(7)\n"
"\n"
"SUB Greet(name AS STRING)\n"
" PRINT \"Hello, \" & name & \"!\"\n"
"END SUB\n"
"\n"
"FUNCTION Square(x AS INTEGER) AS INTEGER\n"
" Square = x * x\n"
"END FUNCTION\n"
);
// Test 8: SELECT CASE
runProgram("SELECT CASE",
"DIM grade AS STRING\n"
"grade = \"B\"\n"
"SELECT CASE grade\n"
" CASE \"A\"\n"
" PRINT \"Excellent\"\n"
" CASE \"B\", \"C\"\n"
" PRINT \"Good\"\n"
" CASE ELSE\n"
" PRINT \"Other\"\n"
"END SELECT\n"
);
// Test 9: Fibonacci
runProgram("Fibonacci",
"DIM a AS INTEGER\n"
"DIM b AS INTEGER\n"
"DIM temp AS INTEGER\n"
"DIM i AS INTEGER\n"
"a = 0\n"
"b = 1\n"
"FOR i = 1 TO 10\n"
" PRINT a;\n"
" temp = a + b\n"
" a = b\n"
" b = temp\n"
"NEXT i\n"
"PRINT\n"
);
// Test 10: Math functions
runProgram("Math Functions",
"PRINT ABS(-42)\n"
"PRINT SQR(144)\n"
"PRINT INT(3.7)\n"
);
// Test 11: File I/O
runProgram("File I/O",
"OPEN \"/tmp/dvxbasic_test.txt\" FOR OUTPUT AS #1\n"
"PRINT #1, \"Hello from BASIC!\"\n"
"PRINT #1, \"Line two\"\n"
"PRINT #1, \"42\"\n"
"CLOSE #1\n"
"\n"
"DIM line$ AS STRING\n"
"DIM count AS INTEGER\n"
"count = 0\n"
"OPEN \"/tmp/dvxbasic_test.txt\" FOR INPUT AS #1\n"
"DO WHILE NOT EOF(#1)\n"
" INPUT #1, line$\n"
" PRINT line$\n"
" count = count + 1\n"
"LOOP\n"
"CLOSE #1\n"
"PRINT count;\n"
"PRINT \"lines read\"\n"
);
// Test 12: LINE INPUT# and APPEND
runProgram("LINE INPUT and APPEND",
"OPEN \"/tmp/dvxbasic_test2.txt\" FOR OUTPUT AS #2\n"
"PRINT #2, \"First line\"\n"
"CLOSE #2\n"
"\n"
"OPEN \"/tmp/dvxbasic_test2.txt\" FOR APPEND AS #2\n"
"PRINT #2, \"Appended line\"\n"
"CLOSE #2\n"
"\n"
"DIM s$ AS STRING\n"
"OPEN \"/tmp/dvxbasic_test2.txt\" FOR INPUT AS #2\n"
"LINE INPUT #2, s$\n"
"PRINT s$\n"
"LINE INPUT #2, s$\n"
"PRINT s$\n"
"CLOSE #2\n"
);
// Test 13: Array -- 1D with default lbound=0
runProgram("1D Array",
"DIM arr(5) AS INTEGER\n"
"DIM i AS INTEGER\n"
"FOR i = 1 TO 5\n"
" arr(i) = i * i\n"
"NEXT i\n"
"FOR i = 1 TO 5\n"
" PRINT arr(i);\n"
"NEXT i\n"
"PRINT\n"
);
// Expected: 1 4 9 16 25
// Test 14: Multi-dimensional array
runProgram("Multi-dim Array",
"DIM m(2, 2) AS INTEGER\n"
"m(1, 1) = 11\n"
"m(1, 2) = 12\n"
"m(2, 1) = 21\n"
"m(2, 2) = 22\n"
"PRINT m(1, 1); m(1, 2); m(2, 1); m(2, 2)\n"
);
// Expected: 11 12 21 22
// Test 15: Array with explicit bounds (TO syntax)
runProgram("Array with TO bounds",
"DIM a(1 TO 3) AS INTEGER\n"
"a(1) = 10\n"
"a(2) = 20\n"
"a(3) = 30\n"
"PRINT a(1); a(2); a(3)\n"
);
// Expected: 10 20 30
// Test 16: LBOUND and UBOUND
runProgram("LBOUND/UBOUND",
"DIM a(5 TO 10) AS INTEGER\n"
"PRINT LBOUND(a); UBOUND(a)\n"
);
// Expected: 5 10
// Test 17: User-defined TYPE
runProgram("TYPE",
"TYPE Point\n"
" x AS INTEGER\n"
" y AS INTEGER\n"
"END TYPE\n"
"DIM p AS Point\n"
"p.x = 10\n"
"p.y = 20\n"
"PRINT p.x; p.y\n"
);
// Expected: 10 20
// Test 18: String array
runProgram("String Array",
"DIM names(3) AS STRING\n"
"names(0) = \"Alice\"\n"
"names(1) = \"Bob\"\n"
"names(2) = \"Charlie\"\n"
"DIM i AS INTEGER\n"
"FOR i = 0 TO 2\n"
" PRINT names(i)\n"
"NEXT i\n"
);
// Expected: Alice / Bob / Charlie
// Test 19: REDIM with PRESERVE
runProgram("REDIM PRESERVE",
"DIM a(3) AS INTEGER\n"
"a(0) = 100\n"
"a(1) = 200\n"
"a(2) = 300\n"
"REDIM PRESERVE a(5) AS INTEGER\n"
"a(4) = 500\n"
"PRINT a(0); a(1); a(2); a(4)\n"
);
// Expected: 100 200 300 500
// Test 20: ERASE
runProgram("ERASE",
"DIM a(3) AS INTEGER\n"
"a(1) = 42\n"
"ERASE a\n"
"DIM b(2) AS INTEGER\n"
"b(1) = 99\n"
"PRINT b(1)\n"
);
// Expected: 99
// Test 21: Array in FOR loop accumulation
runProgram("Array Accumulation",
"DIM sums(5) AS INTEGER\n"
"DIM i AS INTEGER\n"
"DIM j AS INTEGER\n"
"FOR i = 1 TO 5\n"
" sums(i) = 0\n"
" FOR j = 1 TO i\n"
" sums(i) = sums(i) + j\n"
" NEXT j\n"
"NEXT i\n"
"FOR i = 1 TO 5\n"
" PRINT sums(i);\n"
"NEXT i\n"
"PRINT\n"
);
// Expected: 1 3 6 10 15
// ============================================================
// Batch 1: Control Flow
// ============================================================
// Test: GOTO with forward jump
runProgram("GOTO Forward",
"PRINT \"before\"\n"
"GOTO skip\n"
"PRINT \"skipped\"\n"
"skip:\n"
"PRINT \"after\"\n"
);
// Expected: before / after
// Test: GOTO with backward jump
runProgram("GOTO Backward",
"DIM n AS INTEGER\n"
"n = 0\n"
"top:\n"
"n = n + 1\n"
"IF n < 5 THEN GOTO top\n"
"PRINT n\n"
);
// Expected: 5
// Test: GOSUB/RETURN
runProgram("GOSUB/RETURN",
"DIM x AS INTEGER\n"
"x = 10\n"
"GOSUB dbl\n"
"PRINT x\n"
"END\n"
"dbl:\n"
"x = x * 2\n"
"RETURN\n"
);
// Expected: 20
// Test: ON ERROR GOTO -- verify error handler catches errors
// and ERR returns the error number
runProgram("ON ERROR GOTO",
"ON ERROR GOTO handler\n"
"PRINT 10 / 0\n"
"END\n"
"handler:\n"
"PRINT \"caught\"\n"
"PRINT ERR\n"
);
// Expected: caught / 11
// Test: Single-line IF
runProgram("Single-line IF",
"DIM x AS INTEGER\n"
"x = 42\n"
"IF x > 10 THEN PRINT \"big\"\n"
"IF x < 10 THEN PRINT \"small\"\n"
"IF x = 42 THEN PRINT \"exact\" ELSE PRINT \"nope\"\n"
);
// Expected: big / exact
// Test: Multi-statement line with :
runProgram("Multi-statement :",
"DIM x AS INTEGER\n"
"DIM y AS INTEGER\n"
"x = 1 : y = 2 : PRINT x + y\n"
);
// Expected: 3
// ============================================================
// Batch 2: Misc Features
// ============================================================
// Test: SWAP
runProgram("SWAP",
"DIM a AS INTEGER\n"
"DIM b AS INTEGER\n"
"a = 10\n"
"b = 20\n"
"SWAP a, b\n"
"PRINT a;\n"
"PRINT b\n"
);
// Expected: 20 10
// Test: TIMER (returns number > 0)
runProgram("TIMER",
"DIM t AS DOUBLE\n"
"t = TIMER\n"
"IF t > 0 THEN PRINT \"ok\"\n"
);
// Expected: ok
// Test: DATE$ (returns non-empty string)
runProgram("DATE$",
"DIM d$ AS STRING\n"
"d$ = DATE$\n"
"IF LEN(d$) > 0 THEN PRINT \"ok\"\n"
);
// Expected: ok
// Test: TIME$ (returns non-empty string)
runProgram("TIME$",
"DIM t$ AS STRING\n"
"t$ = TIME$\n"
"IF LEN(t$) > 0 THEN PRINT \"ok\"\n"
);
// Expected: ok
// Test: ENVIRON$
runProgram("ENVIRON$",
"DIM p$ AS STRING\n"
"p$ = ENVIRON$(\"HOME\")\n"
"IF LEN(p$) > 0 THEN PRINT \"ok\"\n"
);
// Expected: ok
// ============================================================
// Batch 3: New features (DATA/READ/RESTORE, DIM SHARED,
// STATIC, DEF FN, OPTION BASE)
// ============================================================
// Test: DATA/READ/RESTORE
runProgram("DATA/READ/RESTORE",
"DATA 10, 20, \"hello\"\n"
"DIM a AS INTEGER\n"
"DIM b AS INTEGER\n"
"DIM c AS STRING\n"
"READ a, b, c\n"
"PRINT a; b;\n"
"PRINT c\n"
"RESTORE\n"
"READ a\n"
"PRINT a\n"
);
// Expected: 10 20 hello / 10
// Test: DIM SHARED
runProgram("DIM SHARED",
"DIM SHARED count AS INTEGER\n"
"count = 0\n"
"CALL Increment\n"
"CALL Increment\n"
"CALL Increment\n"
"PRINT count\n"
"SUB Increment\n"
" count = count + 1\n"
"END SUB\n"
);
// Expected: 3
// Test: STATIC
runProgram("STATIC",
"CALL Counter\n"
"CALL Counter\n"
"CALL Counter\n"
"SUB Counter\n"
" STATIC n AS INTEGER\n"
" n = n + 1\n"
" PRINT n;\n"
"END SUB\n"
"PRINT\n"
);
// Expected: 1 2 3
// Test: DEF FN
runProgram("DEF FN",
"DEF FNdouble(x AS INTEGER) = x * 2\n"
"PRINT FNdouble(5)\n"
"PRINT FNdouble(21)\n"
);
// Expected: 10 / 42
// Test: OPTION BASE
runProgram("OPTION BASE",
"OPTION BASE 1\n"
"DIM arr(3) AS INTEGER\n"
"arr(1) = 10\n"
"arr(3) = 30\n"
"PRINT arr(1); arr(3)\n"
);
// Expected: 10 30
// Test: DATA with mixed types
runProgram("DATA mixed types",
"DATA 100, 3.14, \"world\"\n"
"DIM x AS INTEGER\n"
"DIM y AS DOUBLE\n"
"DIM z AS STRING\n"
"READ x, y, z\n"
"PRINT x\n"
"PRINT z\n"
);
// Expected: 100 / world
// Test: Multiple DATA statements scattered
runProgram("DATA scattered",
"DIM a AS INTEGER\n"
"DIM b AS INTEGER\n"
"DIM c AS INTEGER\n"
"DATA 1, 2\n"
"READ a, b\n"
"DATA 3\n"
"READ c\n"
"PRINT a; b; c\n"
);
// Expected: 1 2 3
// Test: DIM SHARED with SUB modifying shared variable
runProgram("DIM SHARED multi",
"DIM SHARED total AS INTEGER\n"
"DIM SHARED msg AS STRING\n"
"total = 100\n"
"msg = \"start\"\n"
"CALL Modify\n"
"PRINT total\n"
"PRINT msg\n"
"SUB Modify\n"
" total = total + 50\n"
" msg = \"done\"\n"
"END SUB\n"
);
// Expected: 150 / done
// ============================================================
// Batch 4: New I/O and string features
// ============================================================
// Test: WRITE #
runProgram("WRITE #",
"OPEN \"/tmp/dvxbasic_write.txt\" FOR OUTPUT AS #1\n"
"WRITE #1, 10, \"hello\", 3.14\n"
"CLOSE #1\n"
"OPEN \"/tmp/dvxbasic_write.txt\" FOR INPUT AS #1\n"
"DIM s AS STRING\n"
"LINE INPUT #1, s\n"
"PRINT s\n"
"CLOSE #1\n"
);
// Expected: 10,"hello",3.14
// Test: FREEFILE
runProgram("FREEFILE",
"DIM f AS INTEGER\n"
"f = FREEFILE\n"
"PRINT f\n"
);
// Expected: 1
// Test: PRINT USING numeric
runProgram("PRINT USING numeric",
"PRINT USING \"###.##\"; 3.14159\n"
);
// Expected: 3.14
// Test: PRINT USING string
runProgram("PRINT USING string",
"PRINT USING \"!\"; \"Hello\"\n"
);
// Expected: H
// Test: SPC and TAB in PRINT
runProgram("SPC/TAB",
"PRINT SPC(3); \"hi\"\n"
);
// Expected: hi
// Test: Fixed-length string
runProgram("STRING * n",
"DIM s AS STRING * 5\n"
"s = \"Hi\"\n"
"PRINT \"[\" & s & \"]\"\n"
"PRINT LEN(s)\n"
);
// Expected: [Hi ] / 5
// Test: MID$ statement
runProgram("MID$ statement",
"DIM s AS STRING\n"
"s = \"Hello World\"\n"
"MID$(s, 7, 5) = \"BASIC\"\n"
"PRINT s\n"
);
// Expected: Hello BASIC
// Test: OPEN FOR BINARY / GET / PUT
runProgram("BINARY GET/PUT",
"DIM v AS INTEGER\n"
"OPEN \"/tmp/dvxbasic_bin.tmp\" FOR BINARY AS #1\n"
"v = 12345\n"
"PUT #1, , v\n"
"SEEK #1, 1\n"
"DIM r AS INTEGER\n"
"GET #1, , r\n"
"PRINT r\n"
"CLOSE #1\n"
);
// Expected: 12345
// Test: LOF and LOC
runProgram("LOF/LOC",
"OPEN \"/tmp/dvxbasic_lof.txt\" FOR OUTPUT AS #1\n"
"PRINT #1, \"test\"\n"
"CLOSE #1\n"
"OPEN \"/tmp/dvxbasic_lof.txt\" FOR INPUT AS #1\n"
"DIM sz AS LONG\n"
"sz = LOF(1)\n"
"IF sz > 0 THEN PRINT \"ok\"\n"
"CLOSE #1\n"
);
// Expected: ok
// Test: INPUT$(n, #channel)
runProgram("INPUT$",
"OPEN \"/tmp/dvxbasic_inp.txt\" FOR OUTPUT AS #1\n"
"PRINT #1, \"ABCDEF\"\n"
"CLOSE #1\n"
"OPEN \"/tmp/dvxbasic_inp.txt\" FOR INPUT AS #1\n"
"DIM s AS STRING\n"
"s = INPUT$(3, #1)\n"
"PRINT s\n"
"CLOSE #1\n"
);
// Expected: ABC
// Test: SEEK function form
runProgram("SEEK function",
"OPEN \"/tmp/dvxbasic_seek.txt\" FOR OUTPUT AS #1\n"
"PRINT #1, \"test\"\n"
"CLOSE #1\n"
"OPEN \"/tmp/dvxbasic_seek.txt\" FOR BINARY AS #1\n"
"DIM p AS LONG\n"
"p = SEEK(1)\n"
"IF p = 1 THEN PRINT \"ok\"\n"
"CLOSE #1\n"
);
// Expected: ok
// Test: ON n GOTO
runProgram("ON n GOTO",
"DIM n AS INTEGER\n"
"n = 2\n"
"ON n GOTO ten, twenty, thirty\n"
"PRINT \"none\"\n"
"GOTO done\n"
"ten:\n"
"PRINT \"ten\"\n"
"GOTO done\n"
"twenty:\n"
"PRINT \"twenty\"\n"
"GOTO done\n"
"thirty:\n"
"PRINT \"thirty\"\n"
"done:\n"
);
// Expected: twenty
// Test: ON n GOTO (no match)
runProgram("ON n GOTO no match",
"DIM n AS INTEGER\n"
"n = 5\n"
"ON n GOTO aa, bb\n"
"PRINT \"fallthrough\"\n"
"GOTO done2\n"
"aa:\n"
"PRINT \"aa\"\n"
"GOTO done2\n"
"bb:\n"
"PRINT \"bb\"\n"
"done2:\n"
);
// Expected: fallthrough
// Test: ON n GOSUB
runProgram("ON n GOSUB",
"DIM n AS INTEGER\n"
"DIM result AS INTEGER\n"
"result = 0\n"
"n = 2\n"
"ON n GOSUB addTen, addTwenty, addThirty\n"
"PRINT result\n"
"GOTO endProg\n"
"addTen:\n"
"result = result + 10\n"
"RETURN\n"
"addTwenty:\n"
"result = result + 20\n"
"RETURN\n"
"addThirty:\n"
"result = result + 30\n"
"RETURN\n"
"endProg:\n"
);
// Expected: 20
// Test: FORMAT$
runProgram("FORMAT$",
"PRINT FORMAT$(1234.5, \"#,##0.00\")\n"
"PRINT FORMAT$(0.5, \"0.00\")\n"
"PRINT FORMAT$(-42, \"+#0\")\n"
"PRINT FORMAT$(0.75, \"percent\")\n"
);
// Expected: 1,234.50\n0.50\n-42\n75%
// Test: SHELL as function expression
runProgram("SHELL function",
"DIM r AS INTEGER\n"
"r = SHELL(\"echo hello > /dev/null\")\n"
"IF r = 0 THEN PRINT \"ok\"\n"
);
// Expected: ok
// Test: SHELL as statement
runProgram("SHELL statement",
"SHELL \"echo hello > /dev/null\"\n"
"PRINT \"done\"\n"
);
// Expected: done
// Test: OPTION COMPARE TEXT
runProgram("OPTION COMPARE TEXT",
"OPTION COMPARE TEXT\n"
"IF \"hello\" = \"HELLO\" THEN\n"
" PRINT \"equal\"\n"
"ELSE\n"
" PRINT \"not equal\"\n"
"END IF\n"
"IF \"abc\" < \"XYZ\" THEN\n"
" PRINT \"less\"\n"
"END IF\n"
);
// Expected: equal\nless
// Test: OPTION COMPARE BINARY (default)
runProgram("OPTION COMPARE BINARY",
"OPTION COMPARE BINARY\n"
"IF \"hello\" = \"HELLO\" THEN\n"
" PRINT \"equal\"\n"
"ELSE\n"
" PRINT \"not equal\"\n"
"END IF\n"
);
// Expected: not equal
// Test: EQV operator
runProgram("EQV operator",
"PRINT -1 EQV -1\n"
"PRINT 0 EQV 0\n"
"PRINT -1 EQV 0\n"
"PRINT 0 EQV -1\n"
);
// Expected: -1\n-1\n0\n0
// Test: IMP operator
runProgram("IMP operator",
"PRINT 0 IMP -1\n"
"PRINT -1 IMP 0\n"
"PRINT -1 IMP -1\n"
"PRINT 0 IMP 0\n"
);
// Expected: -1\n0\n-1\n-1
// Test: PRINT USING advanced patterns
runProgram("PRINT USING advanced",
"PRINT USING \"**#,##0.00\"; 1234.5\n"
"PRINT USING \"$$#,##0.00\"; 42.5\n"
"PRINT USING \"+###.##\"; 42.5\n"
"PRINT USING \"+###.##\"; -42.5\n"
"PRINT USING \"###.##-\"; -42.5\n"
"PRINT USING \"###.##-\"; 42.5\n"
"PRINT USING \"#.##^^^^\"; 1234.5\n"
);
// Test: DEFINT
runProgram("DEFINT",
"DEFINT A-Z\n"
"a = 42\n"
"b = 3.7\n"
"PRINT a; b\n"
);
// Test: DEFSTR
runProgram("DEFSTR",
"DEFSTR S\n"
"s = \"hello\"\n"
"PRINT s\n"
);
// Test: DEFINT range
runProgram("DEFINT range",
"DEFINT I-N\n"
"i = 10\n"
"j = 20\n"
"x = 3.14\n"
"PRINT i; j; x\n"
);
// Test: OPTION EXPLICIT success
runProgram("OPTION EXPLICIT ok",
"OPTION EXPLICIT\n"
"DIM x AS INTEGER\n"
"x = 42\n"
"PRINT x\n"
);
// Test: OPTION EXPLICIT failure (should error)
{
printf("=== OPTION EXPLICIT error ===\n");
const char *src =
"OPTION EXPLICIT\n"
"x = 42\n";
int32_t len = (int32_t)strlen(src);
BasParserT parser;
basParserInit(&parser, src, len);
bool ok = basParse(&parser);
if (!ok) {
printf("Correctly caught: %s\n", parser.error);
} else {
printf("ERROR: should have failed\n");
}
basParserFree(&parser);
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");
}
// Regression test: App.Data must parse even though Data is a BASIC keyword
runProgram("App.Data parses",
"DIM p AS STRING\n"
"p = App.Data\n"
"PRINT p\n"
);
// Regression test: RGB(r,g,b) packs into 0x00RRGGBB
runProgram("RGB packing",
"PRINT RGB(255, 128, 0)\n"
"PRINT RGB(0, 255, 0)\n"
"PRINT RGB(0, 0, 255)\n"
);
// Expected: 16744448 (0xFF8000), 65280 (0x00FF00), 255 (0x0000FF)
// Regression test: GetRed/Green/Blue round-trip
runProgram("RGB round-trip",
"DIM c AS LONG\n"
"c = RGB(17, 99, 200)\n"
"PRINT GetRed(c); GetGreen(c); GetBlue(c)\n"
);
// Expected: 17 99 200
// Regression test: MsgBox function accepts optional title (3rd arg)
// The test harness's default msgBox callback returns 1; we just verify
// the program compiles and runs.
runProgram("MsgBox 3-arg compile",
"DIM r AS INTEGER\n"
"r = MsgBox(\"hi\", 0, \"My Title\")\n"
"PRINT \"ok\"\n"
);
// Regression test: CONST accepts AS type
runProgram("CONST AS type",
"CONST PI AS DOUBLE = 3.14159\n"
"CONST N AS INTEGER = 42\n"
"PRINT PI\n"
"PRINT N\n"
);
// Regression test: PRINT #ch with ; separator
runProgram("PRINT # with semicolon",
"OPEN \"/tmp/dvxbasic_psemi.txt\" FOR OUTPUT AS #1\n"
"PRINT #1, \"Line one\"\n"
"PRINT #1, \"ans = \"; 42\n"
"PRINT #1, \"x\"; \"y\"; \"z\"\n"
"CLOSE #1\n"
"DIM s AS STRING\n"
"OPEN \"/tmp/dvxbasic_psemi.txt\" FOR INPUT AS #1\n"
"DO WHILE NOT EOF(1)\n"
" LINE INPUT #1, s\n"
" PRINT s\n"
"LOOP\n"
"CLOSE #1\n"
);
// Expected: Line one / ans = 42 / xyz
// Regression test: IniRead$ with $ suffix must tokenize as keyword
runProgram("IniRead$ tokenizes",
"DIM v AS STRING\n"
"v = IniRead$(\"/tmp/nofile.ini\", \"S\", \"K\", \"default\")\n"
"PRINT v\n"
);
// Expected: default (file doesn't exist)
// Regression test: SUB extern call without parens must emit OP_CALL_EXTERN
// (previously emitted OP_CALL which called nothing, because no internal
// proc at address 0 existed for the extern.)
{
printf("=== DECLARE LIBRARY SUB no-parens ===\n");
const char *src =
"DECLARE LIBRARY \"basrt\"\n"
" DECLARE SUB DoIt(BYVAL s AS STRING)\n"
"END DECLARE\n"
"DoIt \"hello\"\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);
bool found = false;
for (int32_t i = 0; i < mod->codeLen; i++) {
if (mod->code[i] == OP_CALL_EXTERN) {
found = true;
break;
}
}
printf(found ? "PASS: OP_CALL_EXTERN emitted\n" : "FAIL: OP_CALL_EXTERN not emitted\n");
basModuleFree(mod);
}
basParserFree(&parser);
printf("\n");
}
// Test: Procedure table populated for SUBs and FUNCTIONs
{
printf("=== Procedure table ===\n");
const char *src =
"SUB Command1_Click\n"
" PRINT \"Clicked!\"\n"
"END SUB\n"
"\n"
"SUB Form1_Load\n"
" PRINT \"Loaded\"\n"
"END SUB\n"
"\n"
"FUNCTION AddNums(a AS INTEGER, b AS INTEGER) AS INTEGER\n"
" AddNums = a + b\n"
"END FUNCTION\n"
"\n"
"PRINT \"main\"\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);
printf("procCount = %d\n", mod->procCount);
for (int32_t i = 0; i < mod->procCount; i++) {
printf(" [%d] %s addr=%d params=%d func=%d\n",
i, mod->procs[i].name, mod->procs[i].codeAddr,
mod->procs[i].paramCount, mod->procs[i].isFunction);
}
// Test lookup
const BasProcEntryT *p = basModuleFindProc(mod, "command1_click");
if (p) {
printf("Found Command1_Click at addr %d\n", p->codeAddr);
} else {
printf("FAIL: Command1_Click not found!\n");
}
p = basModuleFindProc(mod, "FORM1_LOAD");
if (p) {
printf("Found Form1_Load at addr %d\n", p->codeAddr);
} else {
printf("FAIL: Form1_Load not found!\n");
}
// Test basVmCallSub
BasVmT *vm = basVmCreate();
basVmLoadModule(vm, mod);
vm->callStack[0].localCount = mod->globalCount > 64 ? 64 : mod->globalCount;
vm->callDepth = 1;
p = basModuleFindProc(mod, "Command1_Click");
if (p) {
printf("Calling Command1_Click via basVmCallSub: ");
bool callOk = basVmCallSub(vm, p->codeAddr);
printf("result=%s\n", callOk ? "ok" : "FAIL");
}
basVmDestroy(vm);
basModuleFree(mod);
}
basParserFree(&parser);
printf("\n");
}
// ByRef parameters
runProgram("ByRef parameters",
"DECLARE SUB AddTen(n AS INTEGER)\n"
"DECLARE SUB ChangeStr(s AS STRING)\n"
"DECLARE SUB NoChange(BYVAL n AS INTEGER)\n"
"\n"
"DIM x AS INTEGER\n"
"x = 10\n"
"PRINT \"Before: x =\"; x\n"
"AddTen x\n"
"PRINT \"After AddTen: x =\"; x\n"
"\n"
"DIM a AS STRING\n"
"a = \"hello\"\n"
"PRINT \"Before: a = \"; a\n"
"ChangeStr a\n"
"PRINT \"After ChangeStr: a = \"; a\n"
"\n"
"' ByVal should NOT modify caller\n"
"DIM y AS INTEGER\n"
"y = 100\n"
"PRINT \"Before: y =\"; y\n"
"NoChange y\n"
"PRINT \"After NoChange: y =\"; y\n"
"\n"
"' Expression arg to ByRef param: effectively ByVal\n"
"DIM z AS INTEGER\n"
"z = 5\n"
"AddTen(z + 0)\n"
"PRINT \"After AddTen(z+0): z =\"; z\n"
"\n"
"SUB AddTen(n AS INTEGER)\n"
" n = n + 10\n"
"END SUB\n"
"\n"
"SUB ChangeStr(s AS STRING)\n"
" s = s + \" world\"\n"
"END SUB\n"
"\n"
"SUB NoChange(BYVAL n AS INTEGER)\n"
" n = n + 999\n"
"END SUB\n"
);
// ============================================================
// Coverage: String functions
// ============================================================
runProgram("LCASE$",
"PRINT LCASE$(\"HELLO WORLD\")\n"
);
// Expected: hello world
runProgram("TRIM$ LTRIM$ RTRIM$",
"PRINT \"[\" & TRIM$(\" hi \") & \"]\"\n"
"PRINT \"[\" & LTRIM$(\" hi \") & \"]\"\n"
"PRINT \"[\" & RTRIM$(\" hi \") & \"]\"\n"
);
// Expected: [hi] / [hi ] / [ hi]
runProgram("INSTR 2-arg",
"PRINT INSTR(\"hello world\", \"world\")\n"
"PRINT INSTR(\"hello world\", \"xyz\")\n"
);
// Expected: 7 / 0
runProgram("INSTR 3-arg",
"PRINT INSTR(5, \"abcabc\", \"bc\")\n"
"PRINT INSTR(1, \"abcabc\", \"bc\")\n"
);
// Expected: 5 / 2
runProgram("CHR$ and ASC",
"PRINT CHR$(65)\n"
"PRINT ASC(\"Z\")\n"
);
// Expected: A / 90
runProgram("SPACE$",
"PRINT \"[\" & SPACE$(5) & \"]\"\n"
);
// Expected: [ ]
runProgram("STRING$",
"PRINT STRING$(5, \"*\")\n"
);
// Expected: *****
runProgram("HEX$",
"PRINT HEX$(255)\n"
"PRINT HEX$(16)\n"
);
// Expected: FF / 10
runProgram("VAL",
"PRINT VAL(\"3.14\")\n"
"PRINT VAL(\"42\")\n"
"PRINT VAL(\"abc\")\n"
);
// Expected: 3.14 / 42 / 0
runProgram("STR$",
"PRINT \"[\" & STR$(42) & \"]\"\n"
"PRINT \"[\" & STR$(-7) & \"]\"\n"
);
// Expected: [ 42] / [-7]
runProgram("MID$ 2-arg",
"PRINT MID$(\"hello world\", 7)\n"
);
// Expected: world
runProgram("String concat with +",
"DIM a AS STRING\n"
"DIM b AS STRING\n"
"a = \"hello\"\n"
"b = \" world\"\n"
"PRINT a + b\n"
);
// Expected: hello world
// ============================================================
// Coverage: Math functions
// ============================================================
runProgram("Trig functions",
"DIM pi AS DOUBLE\n"
"pi = ATN(1) * 4\n"
"PRINT INT(SIN(pi / 2) * 1000)\n"
"PRINT INT(COS(0) * 1000)\n"
"PRINT INT(TAN(pi / 4) * 1000)\n"
);
// Expected: 1000 / 1000 / 1000
runProgram("LOG and EXP",
"PRINT INT(LOG(1))\n"
"PRINT INT(EXP(0))\n"
"PRINT INT(EXP(1) * 100)\n"
);
// Expected: 0 / 1 / 271
runProgram("FIX and SGN",
"PRINT FIX(3.7)\n"
"PRINT FIX(-3.7)\n"
"PRINT SGN(42)\n"
"PRINT SGN(-5)\n"
"PRINT SGN(0)\n"
);
// Expected: 3 / -3 / 1 / -1 / 0
runProgram("RND and RANDOMIZE",
"RANDOMIZE 12345\n"
"DIM r AS DOUBLE\n"
"r = RND\n"
"IF r >= 0 AND r < 1 THEN PRINT \"ok\"\n"
);
// Expected: ok
// ============================================================
// Coverage: Loop variants
// ============================================================
runProgram("DO UNTIL pre-test",
"DIM n AS INTEGER\n"
"n = 1\n"
"DO UNTIL n > 5\n"
" PRINT n;\n"
" n = n + 1\n"
"LOOP\n"
"PRINT\n"
);
// Expected: 1 2 3 4 5
runProgram("DO LOOP WHILE post-test",
"DIM n AS INTEGER\n"
"n = 1\n"
"DO\n"
" PRINT n;\n"
" n = n + 1\n"
"LOOP WHILE n <= 5\n"
"PRINT\n"
);
// Expected: 1 2 3 4 5
runProgram("DO LOOP UNTIL post-test",
"DIM n AS INTEGER\n"
"n = 1\n"
"DO\n"
" PRINT n;\n"
" n = n + 1\n"
"LOOP UNTIL n > 5\n"
"PRINT\n"
);
// Expected: 1 2 3 4 5
runProgram("WHILE WEND",
"DIM n AS INTEGER\n"
"n = 1\n"
"WHILE n <= 5\n"
" PRINT n;\n"
" n = n + 1\n"
"WEND\n"
"PRINT\n"
);
// Expected: 1 2 3 4 5
runProgram("FOR with negative STEP",
"DIM i AS INTEGER\n"
"FOR i = 5 TO 1 STEP -1\n"
" PRINT i;\n"
"NEXT i\n"
"PRINT\n"
);
// Expected: 5 4 3 2 1
runProgram("FOR with STEP 2",
"DIM i AS INTEGER\n"
"FOR i = 0 TO 10 STEP 2\n"
" PRINT i;\n"
"NEXT i\n"
"PRINT\n"
);
// Expected: 0 2 4 6 8 10
// ============================================================
// Coverage: EXIT statements
// ============================================================
runProgram("EXIT FOR",
"DIM i AS INTEGER\n"
"FOR i = 1 TO 100\n"
" IF i = 3 THEN EXIT FOR\n"
" PRINT i;\n"
"NEXT i\n"
"PRINT\n"
"PRINT \"after\"\n"
);
// Expected: 1 2 / after
// Regression test: FOR loop inside a SUB with local loop variable
runProgram("FOR inside SUB with local",
"CALL MySub\n"
"SUB MySub\n"
" DIM i AS LONG\n"
" FOR i = 0 TO 3\n"
" PRINT i;\n"
" NEXT i\n"
" PRINT\n"
"END SUB\n"
);
// Expected: 0 1 2 3
// Regression test: resedit pattern -- FOR in SUB with n = FUNC(...)
runProgram("FOR with func-returned limit",
"CALL DoIt\n"
"SUB DoIt\n"
" DIM n AS LONG\n"
" n = GetCount()\n"
" DIM i AS LONG\n"
" FOR i = 0 TO n - 1\n"
" PRINT i;\n"
" NEXT i\n"
" PRINT\n"
"END SUB\n"
"FUNCTION GetCount() AS LONG\n"
" GetCount = 4\n"
"END FUNCTION\n"
);
// Expected: 0 1 2 3
// Regression test: resedit pattern -- FOR in SUB inside a FORM
runProgram("FOR in SUB inside FORM",
"BEGINFORM \"Form1\"\n"
"DIM formVar AS LONG\n"
"formVar = 42\n"
"CALL DoIt\n"
"SUB DoIt\n"
" DIM n AS LONG\n"
" n = 4\n"
" DIM ix AS LONG\n"
" FOR ix = 0 TO n - 1\n"
" PRINT ix;\n"
" NEXT ix\n"
" PRINT\n"
"END SUB\n"
"ENDFORM\n"
);
// Expected: 0 1 2 3
runProgram("EXIT DO",
"DIM n AS INTEGER\n"
"n = 0\n"
"DO\n"
" n = n + 1\n"
" IF n = 4 THEN EXIT DO\n"
" PRINT n;\n"
"LOOP\n"
"PRINT\n"
"PRINT \"after\"\n"
);
// Expected: 1 2 3 / after
runProgram("EXIT SUB",
"DECLARE SUB EarlyReturn(n AS INTEGER)\n"
"EarlyReturn 1\n"
"EarlyReturn 5\n"
"EarlyReturn 2\n"
"SUB EarlyReturn(n AS INTEGER)\n"
" IF n > 3 THEN EXIT SUB\n"
" PRINT n;\n"
"END SUB\n"
"PRINT\n"
);
// Expected: 1 2
runProgram("EXIT FUNCTION",
"DECLARE FUNCTION Clamp(n AS INTEGER) AS INTEGER\n"
"PRINT Clamp(5)\n"
"PRINT Clamp(200)\n"
"FUNCTION Clamp(n AS INTEGER) AS INTEGER\n"
" IF n > 100 THEN\n"
" Clamp = 100\n"
" EXIT FUNCTION\n"
" END IF\n"
" Clamp = n\n"
"END FUNCTION\n"
);
// Expected: 5 / 100
// ============================================================
// Coverage: CONST
// ============================================================
runProgram("CONST",
"CONST PI = 3.14159\n"
"CONST MAX_SIZE = 100\n"
"CONST GREETING = \"hello\"\n"
"PRINT INT(PI * 100)\n"
"PRINT MAX_SIZE\n"
"PRINT GREETING\n"
);
// Expected: 314 / 100 / hello
// ============================================================
// Coverage: END statement
// ============================================================
runProgram("END statement",
"PRINT \"before\"\n"
"END\n"
"PRINT \"after\"\n"
);
// Expected: before
// ============================================================
// Coverage: Boolean literals
// ============================================================
runProgram("True and False",
"DIM b AS BOOLEAN\n"
"b = True\n"
"IF b THEN PRINT \"yes\"\n"
"b = False\n"
"IF NOT b THEN PRINT \"no\"\n"
"PRINT True\n"
"PRINT False\n"
);
// Expected: yes / no / -1 / 0
// ============================================================
// Coverage: NOT operator
// ============================================================
runProgram("NOT operator",
"PRINT NOT 0\n"
"PRINT NOT -1\n"
"DIM x AS INTEGER\n"
"x = 5\n"
"IF NOT (x > 10) THEN PRINT \"small\"\n"
);
// Expected: -1 / 0 / small
// ============================================================
// Coverage: AND OR XOR bitwise
// ============================================================
runProgram("Bitwise AND OR XOR",
"PRINT 15 AND 9\n"
"PRINT 12 OR 3\n"
"PRINT 15 XOR 9\n"
);
// Expected: 9 / 15 / 6
// ============================================================
// Coverage: SLEEP
// ============================================================
runProgram("SLEEP",
"SLEEP 0\n"
"PRINT \"ok\"\n"
);
// Expected: ok
// ============================================================
// Coverage: Error recovery
// ============================================================
runProgram("RESUME NEXT",
"ON ERROR GOTO handler\n"
"DIM x AS INTEGER\n"
"x = 10 / 0\n"
"PRINT \"resumed\"\n"
"END\n"
"handler:\n"
"RESUME NEXT\n"
);
// Expected: resumed
runProgram("RAISE ERROR",
"ON ERROR GOTO handler\n"
"ERROR 999\n"
"PRINT \"should not print\"\n"
"END\n"
"handler:\n"
"PRINT \"caught error\"; ERR\n"
);
// Expected: caught error 999
// ============================================================
// Coverage: Type suffixes and hex literals
// ============================================================
runProgram("Type suffixes",
"DIM x%\n"
"DIM s$\n"
"x% = 42\n"
"s$ = \"hello\"\n"
"PRINT x%\n"
"PRINT s$\n"
);
// Expected: 42 / hello
runProgram("Hex literals",
"PRINT &HFF\n"
"PRINT &H10\n"
"DIM x AS INTEGER\n"
"x = &H0A\n"
"PRINT x\n"
);
// Expected: 255 / 16 / 10
// ============================================================
// Coverage: Long integer type
// ============================================================
runProgram("Long integer",
"DIM x AS LONG\n"
"x = 100000\n"
"x = x * 2\n"
"PRINT x\n"
);
// Expected: 200000
// ============================================================
// Coverage: REDIM without PRESERVE
// ============================================================
runProgram("REDIM no PRESERVE",
"DIM a(3) AS INTEGER\n"
"a(1) = 99\n"
"REDIM a(5) AS INTEGER\n"
"PRINT a(1)\n"
);
// Expected: 0 (data cleared)
// ============================================================
// Coverage: PRINT variants
// ============================================================
runProgram("PRINT comma separator",
"PRINT 1, 2, 3\n"
);
// Expected: 1<tab>2<tab>3
runProgram("PRINT bare newline",
"PRINT \"a\"\n"
"PRINT\n"
"PRINT \"b\"\n"
);
// Expected: a / (blank) / b
// ============================================================
// Coverage: LET keyword
// ============================================================
runProgram("LET keyword",
"DIM x AS INTEGER\n"
"LET x = 42\n"
"PRINT x\n"
);
// Expected: 42
// ============================================================
// Coverage: Line continuation
// ============================================================
runProgram("Line continuation",
"DIM x AS INTEGER\n"
"x = 1 + _\n"
" 2 + _\n"
" 3\n"
"PRINT x\n"
);
// Expected: 6
// ============================================================
// Coverage: REM comment
// ============================================================
runProgram("REM comment",
"REM This is a comment\n"
"PRINT \"ok\"\n"
"PRINT \"hi\" REM inline comment\n"
);
// Expected: ok / hi
// ============================================================
// Coverage: SELECT CASE with numeric ranges and IS
// ============================================================
runProgram("SELECT CASE numeric",
"DIM x AS INTEGER\n"
"x = 15\n"
"SELECT CASE x\n"
" CASE 10\n"
" PRINT \"ten\"\n"
" CASE 15, 20\n"
" PRINT \"fifteen or twenty\"\n"
" CASE ELSE\n"
" PRINT \"other\"\n"
"END SELECT\n"
);
// Expected: fifteen or twenty
runProgram("CASE TO range",
"DIM x AS INTEGER\n"
"x = 15\n"
"SELECT CASE x\n"
" CASE 1 TO 10\n"
" PRINT \"1-10\"\n"
" CASE 11 TO 20\n"
" PRINT \"11-20\"\n"
" CASE ELSE\n"
" PRINT \"other\"\n"
"END SELECT\n"
);
// Expected: 11-20
runProgram("CASE IS comparison",
"DIM x AS INTEGER\n"
"x = 50\n"
"SELECT CASE x\n"
" CASE IS < 10\n"
" PRINT \"small\"\n"
" CASE IS >= 100\n"
" PRINT \"big\"\n"
" CASE ELSE\n"
" PRINT \"medium\"\n"
"END SELECT\n"
);
// Expected: medium
runProgram("CASE mixed forms",
"DIM x AS INTEGER\n"
"x = 5\n"
"SELECT CASE x\n"
" CASE 1, 2, 3\n"
" PRINT \"low\"\n"
" CASE 4 TO 6\n"
" PRINT \"mid\"\n"
" CASE IS > 6\n"
" PRINT \"high\"\n"
"END SELECT\n"
);
// Expected: mid
// ============================================================
// Coverage: Nested FOR EXIT
// ============================================================
runProgram("Nested FOR EXIT",
"DIM i AS INTEGER\n"
"DIM j AS INTEGER\n"
"FOR i = 1 TO 3\n"
" FOR j = 1 TO 100\n"
" IF j > 2 THEN EXIT FOR\n"
" PRINT i * 10 + j;\n"
" NEXT j\n"
"NEXT i\n"
"PRINT\n"
);
// Expected: 11 12 21 22 31 32
// ============================================================
// Coverage: Mixed type arithmetic coercion
// ============================================================
runProgram("Type coercion",
"DIM d AS DOUBLE\n"
"d = 2.5\n"
"PRINT 7 + d\n"
"PRINT 7 * d\n"
"DIM i AS INTEGER\n"
"DIM f AS SINGLE\n"
"i = 10\n"
"f = 3.0\n"
"PRINT i / f\n"
);
// Expected: 9.5 / 17.5 / 3.333...
// ============================================================
// Coverage: Multiple FUNCTION return paths
// ============================================================
runProgram("FUNCTION return",
"DECLARE FUNCTION Max(a AS INTEGER, b AS INTEGER) AS INTEGER\n"
"PRINT Max(10, 20)\n"
"PRINT Max(30, 5)\n"
"FUNCTION Max(a AS INTEGER, b AS INTEGER) AS INTEGER\n"
" IF a > b THEN\n"
" Max = a\n"
" ELSE\n"
" Max = b\n"
" END IF\n"
"END FUNCTION\n"
);
// Expected: 20 / 30
// ============================================================
// Coverage: Recursive FUNCTION
// ============================================================
runProgram("Recursive FUNCTION",
"DECLARE FUNCTION Fact(n AS INTEGER) AS LONG\n"
"PRINT Fact(1)\n"
"PRINT Fact(5)\n"
"PRINT Fact(10)\n"
"FUNCTION Fact(n AS INTEGER) AS LONG\n"
" IF n <= 1 THEN\n"
" Fact = 1\n"
" ELSE\n"
" Fact = n * Fact(n - 1)\n"
" END IF\n"
"END FUNCTION\n"
);
// Expected: 1 / 120 / 3628800
// ============================================================
// Coverage: String comparison operators
// ============================================================
runProgram("String comparison",
"IF \"abc\" < \"def\" THEN PRINT \"less\"\n"
"IF \"xyz\" > \"abc\" THEN PRINT \"greater\"\n"
"IF \"abc\" = \"abc\" THEN PRINT \"equal\"\n"
"IF \"abc\" <> \"xyz\" THEN PRINT \"notequal\"\n"
);
// Expected: less / greater / equal / notequal
// ============================================================
// Coverage: Apostrophe comment
// ============================================================
runProgram("Apostrophe comment",
"PRINT \"before\" ' this is a comment\n"
"' full line comment\n"
"PRINT \"after\"\n"
);
// Expected: before / after
// ============================================================
// Coverage: SINGLE data type
// ============================================================
runProgram("SINGLE data type",
"DIM s AS SINGLE\n"
"s = 3.14\n"
"PRINT INT(s * 100)\n"
);
// Expected: 314
// ============================================================
// Coverage: Conversion functions
// ============================================================
runProgram("CINT CLNG CDBL CSNG CSTR",
"PRINT CINT(3.7)\n"
"PRINT CLNG(42)\n"
"PRINT CDBL(3)\n"
"PRINT CSNG(3)\n"
"PRINT \"[\" & CSTR(42) & \"]\"\n"
);
// Expected: 3 / 42 / 3 / 3 / [ 42]
// ============================================================
// Coverage: Me keyword (compilation only -- no form context)
// ============================================================
runProgram("Me keyword compiles",
"' Me compiles to OP_ME_REF\n"
"PRINT \"ok\"\n"
);
// ============================================================
// Coverage: Me.Show / Me.Hide as statements
// ============================================================
{
printf("=== Me.Show / Me.Hide statements ===\n");
// These compile -- runtime needs a form context to actually show/hide
const char *src =
"Sub Form1_Load ()\n"
" Me.Show\n"
" Me.Hide\n"
" PRINT \"me ok\"\n"
"End Sub\n";
int32_t len = (int32_t)strlen(src);
BasParserT parser;
basParserInit(&parser, src, len);
if (!basParse(&parser)) {
printf("COMPILE ERROR: %s\n\n", parser.error);
basParserFree(&parser);
} else {
BasModuleT *mod = basParserBuildModule(&parser);
basParserFree(&parser);
if (mod) {
printf("Compiled OK, proc=%s\n", mod->procs[0].name);
basModuleFree(mod);
}
}
printf("\n");
}
// ============================================================
// Coverage: Me keyword with form context
// ============================================================
{
printf("=== Me keyword with form context ===\n");
const char *src =
"Sub Form1_Load ()\n"
" PRINT \"load fired\"\n"
"End Sub\n"
"\n"
"Sub Command1_Click ()\n"
" PRINT \"click fired\"\n"
"End Sub\n"
"\n"
"Sub Text1_KeyPress (KeyAscii As Integer)\n"
" PRINT \"key:\"; KeyAscii\n"
"End Sub\n"
"\n"
"Sub Picture1_MouseDown (Button As Integer, X As Integer, Y As Integer)\n"
" PRINT \"mouse:\"; Button; X; Y\n"
"End Sub\n";
int32_t len = (int32_t)strlen(src);
BasParserT parser;
basParserInit(&parser, src, len);
if (!basParse(&parser)) {
printf("COMPILE ERROR: %s\n\n", parser.error);
basParserFree(&parser);
} else {
BasModuleT *mod = basParserBuildModule(&parser);
basParserFree(&parser);
if (!mod) {
printf("MODULE BUILD FAILED\n\n");
} else {
BasVmT *vm = basVmCreate();
basVmLoadModule(vm, mod);
vm->callStack[0].localCount = mod->globalCount > 64 ? 64 : mod->globalCount;
vm->callDepth = 1;
// Test: find and call Form1_Load (no args)
const BasProcEntryT *loadProc = basModuleFindProc(mod, "Form1_Load");
if (loadProc) {
basVmCallSub(vm, loadProc->codeAddr);
} else {
printf("Form1_Load not found!\n");
}
// Test: find and call Command1_Click (no args)
const BasProcEntryT *clickProc = basModuleFindProc(mod, "Command1_Click");
if (clickProc) {
basVmCallSub(vm, clickProc->codeAddr);
} else {
printf("Command1_Click not found!\n");
}
// Test: find and call Text1_KeyPress with parameter
const BasProcEntryT *keyProc = basModuleFindProc(mod, "Text1_KeyPress");
if (keyProc) {
BasValueT args[1];
args[0] = basValLong(65); // 'A'
basVmCallSubWithArgs(vm, keyProc->codeAddr, args, 1);
} else {
printf("Text1_KeyPress not found!\n");
}
// Test: find and call Picture1_MouseDown with 3 parameters
const BasProcEntryT *mouseProc = basModuleFindProc(mod, "Picture1_MouseDown");
if (mouseProc) {
BasValueT args[3];
args[0] = basValLong(1); // left button
args[1] = basValLong(50); // X
args[2] = basValLong(100); // Y
basVmCallSubWithArgs(vm, mouseProc->codeAddr, args, 3);
} else {
printf("Picture1_MouseDown not found!\n");
}
// Test: verify proc table
printf("Proc count: %d\n", (int)mod->procCount);
for (int32_t i = 0; i < mod->procCount; i++) {
printf(" [%d] %s (params=%d)\n", (int)i, mod->procs[i].name, (int)mod->procs[i].paramCount);
}
basVmDestroy(vm);
basModuleFree(mod);
}
}
printf("\n");
}
// ============================================================
// Coverage: Event parameter mismatch
// ============================================================
{
printf("=== Event parameter mismatch ===\n");
const char *src =
"Sub NoParams ()\n"
" PRINT \"no params ok\"\n"
"End Sub\n"
"\n"
"Sub OneParam (X As Integer)\n"
" PRINT \"X =\"; X\n"
"End Sub\n"
"\n"
"Sub TwoParams (A As Integer, B As Integer)\n"
" PRINT \"A =\"; A; \"B =\"; B\n"
"End Sub\n";
int32_t len = (int32_t)strlen(src);
BasParserT parser;
basParserInit(&parser, src, len);
if (!basParse(&parser)) {
printf("COMPILE ERROR: %s\n\n", parser.error);
basParserFree(&parser);
} else {
BasModuleT *mod = basParserBuildModule(&parser);
basParserFree(&parser);
if (mod) {
BasVmT *vm = basVmCreate();
basVmLoadModule(vm, mod);
vm->callStack[0].localCount = mod->globalCount > 64 ? 64 : mod->globalCount;
vm->callDepth = 1;
// Call NoParams with no args
const BasProcEntryT *p = basModuleFindProc(mod, "NoParams");
if (p) {
basVmCallSub(vm, p->codeAddr);
}
// Call OneParam with 1 arg
p = basModuleFindProc(mod, "OneParam");
if (p) {
BasValueT args[1];
args[0] = basValLong(42);
basVmCallSubWithArgs(vm, p->codeAddr, args, 1);
}
// Call TwoParams with 2 args
p = basModuleFindProc(mod, "TwoParams");
if (p) {
BasValueT args[2];
args[0] = basValLong(10);
args[1] = basValLong(20);
basVmCallSubWithArgs(vm, p->codeAddr, args, 2);
}
basVmDestroy(vm);
basModuleFree(mod);
}
}
printf("\n");
}
// ============================================================
// Coverage: Procedure extraction from source
// ============================================================
runProgram("Mixed module-level and procedures",
"DIM x As Integer\n"
"x = 10\n"
"PRINT \"module:\"; x\n"
"\n"
"Sub Helper ()\n"
" PRINT \"helper called\"\n"
"End Sub\n"
"\n"
"Function Add (a As Integer, b As Integer) As Integer\n"
" Add = a + b\n"
"End Function\n"
"\n"
"CALL Helper\n"
"PRINT \"sum:\"; Add(3, 4)\n"
);
// ============================================================
// Coverage: ? shortcut for PRINT
// ============================================================
runProgram("? shortcut for PRINT",
"? \"hello\"\n"
"? 1 + 2\n"
);
// ============================================================
// Coverage: Code in .frm format (Sub after form definition)
// ============================================================
{
printf("=== FRM code extraction ===\n");
// Simulate what the IDE does: code section from a .frm
const char *frmCode =
"Sub Form1_Load ()\n"
" PRINT \"form loaded\"\n"
"End Sub\n"
"\n"
"Sub Command1_Click ()\n"
" PRINT \"button clicked\"\n"
"End Sub\n";
int32_t len = (int32_t)strlen(frmCode);
BasParserT parser;
basParserInit(&parser, frmCode, len);
if (!basParse(&parser)) {
printf("COMPILE ERROR: %s\n\n", parser.error);
basParserFree(&parser);
} else {
BasModuleT *mod = basParserBuildModule(&parser);
basParserFree(&parser);
if (mod) {
BasVmT *vm = basVmCreate();
basVmLoadModule(vm, mod);
vm->callStack[0].localCount = mod->globalCount > 64 ? 64 : mod->globalCount;
vm->callDepth = 1;
// Fire both events
const BasProcEntryT *p1 = basModuleFindProc(mod, "Form1_Load");
const BasProcEntryT *p2 = basModuleFindProc(mod, "Command1_Click");
if (p1) { basVmCallSub(vm, p1->codeAddr); }
else { printf("Form1_Load NOT FOUND\n"); }
if (p2) { basVmCallSub(vm, p2->codeAddr); }
else { printf("Command1_Click NOT FOUND\n"); }
basVmDestroy(vm);
basModuleFree(mod);
}
}
printf("\n");
}
// ============================================================
// Coverage: RANDOMIZE TIMER
// ============================================================
runProgram("RANDOMIZE TIMER",
"RANDOMIZE TIMER\n"
"DIM x AS SINGLE\n"
"x = RND\n"
"PRINT \"rnd ok\"\n"
);
// ============================================================
// Coverage: Forward GOSUB
// ============================================================
runProgram("Forward GOSUB",
"GOSUB doWork\n"
"PRINT \"back\"\n"
"END\n"
"doWork:\n"
"PRINT \"working\"\n"
"RETURN\n"
);
// ============================================================
// Coverage: Forward GOTO
// ============================================================
runProgram("Forward GOTO to label",
"GOTO skip\n"
"PRINT \"should not print\"\n"
"skip:\n"
"PRINT \"jumped\"\n"
);
// ============================================================
// Coverage: Array of UDT
// ============================================================
runProgram("Array of UDT",
"TYPE PointT\n"
" x AS INTEGER\n"
" y AS INTEGER\n"
"END TYPE\n"
"\n"
"DIM pts(3) AS PointT\n"
"pts(1).x = 10\n"
"pts(1).y = 20\n"
"pts(2).x = 30\n"
"pts(2).y = 40\n"
"PRINT pts(1).x; pts(1).y\n"
"PRINT pts(2).x; pts(2).y\n"
);
// ============================================================
// Coverage: Nested UDTs
// ============================================================
runProgram("Nested UDT",
"TYPE AddressT\n"
" city AS STRING\n"
" zip AS INTEGER\n"
"END TYPE\n"
"\n"
"TYPE PersonT\n"
" name AS STRING\n"
" addr AS AddressT\n"
"END TYPE\n"
"\n"
"DIM p AS PersonT\n"
"p.name = \"Alice\"\n"
"p.addr.city = \"NYC\"\n"
"p.addr.zip = 10001\n"
"PRINT p.name\n"
"PRINT p.addr.city\n"
"PRINT p.addr.zip\n"
);
// ============================================================
// Coverage: Operator precedence stress
// ============================================================
runProgram("Operator precedence",
"' NOT has highest unary, then AND, OR, XOR, EQV, IMP\n"
"PRINT NOT 0 AND -1\n" // NOT 0 = -1, -1 AND -1 = -1
"PRINT (1 > 0) AND (2 > 1)\n" // True AND True = -1
"PRINT (1 > 0) OR (2 < 1)\n" // True OR False = -1
"PRINT 5 AND 3\n" // bitwise: 1
"PRINT 5 OR 3\n" // bitwise: 7
"PRINT 5 XOR 3\n" // bitwise: 6
"PRINT 2 + 3 * 4 - 1\n" // 2 + 12 - 1 = 13
"PRINT (2 + 3) * (4 - 1)\n" // 5 * 3 = 15
"PRINT -2 ^ 2\n" // -(2^2) = -4 (VB precedence)
);
// ============================================================
// Coverage: Nested FOR with EXIT
// ============================================================
runProgram("Nested FOR with EXIT FOR",
"DIM i AS INTEGER\n"
"DIM j AS INTEGER\n"
"FOR i = 1 TO 3\n"
" FOR j = 1 TO 3\n"
" IF j = 2 THEN EXIT FOR\n"
" PRINT i; j\n"
" NEXT j\n"
"NEXT i\n"
);
// ============================================================
// Coverage: Forward reference CALL (sub defined after use)
// ============================================================
runProgram("Forward CALL reference",
"CALL doWork\n"
"PRINT \"after call\"\n"
"\n"
"Sub doWork ()\n"
" PRINT \"in sub\"\n"
"End Sub\n"
);
// ============================================================
// Coverage: Multiple CONST declarations
// ============================================================
runProgram("Multiple CONST",
"CONST PI = 3.14159\n"
"CONST E = 2.71828\n"
"CONST NAME = \"DVX\"\n"
"PRINT INT(PI * 100)\n"
"PRINT INT(E * 100)\n"
"PRINT NAME\n"
);
// ============================================================
// Coverage: SELECT CASE with strings
// ============================================================
runProgram("SELECT CASE strings",
"DIM s AS STRING\n"
"s = \"hello\"\n"
"SELECT CASE s\n"
" CASE \"world\"\n"
" PRINT \"wrong\"\n"
" CASE \"hello\"\n"
" PRINT \"right\"\n"
" CASE ELSE\n"
" PRINT \"default\"\n"
"END SELECT\n"
);
// ============================================================
// Coverage: Nested IF/ELSEIF chains
// ============================================================
runProgram("Nested IF chains",
"DIM x AS INTEGER\n"
"x = 50\n"
"IF x > 90 THEN\n"
" PRINT \"A\"\n"
"ELSEIF x > 80 THEN\n"
" PRINT \"B\"\n"
"ELSEIF x > 70 THEN\n"
" PRINT \"C\"\n"
"ELSEIF x > 60 THEN\n"
" PRINT \"D\"\n"
"ELSE\n"
" PRINT \"F\"\n"
"END IF\n"
);
// ============================================================
// Coverage: DO WHILE with EXIT DO
// ============================================================
runProgram("DO WHILE with EXIT DO",
"DIM i AS INTEGER\n"
"i = 0\n"
"DO WHILE i < 100\n"
" i = i + 1\n"
" IF i = 5 THEN EXIT DO\n"
"LOOP\n"
"PRINT i\n"
);
// ============================================================
// Coverage: Recursive Fibonacci
// ============================================================
runProgram("Recursive Fibonacci",
"Function Fib (n As Integer) As Integer\n"
" IF n <= 1 THEN\n"
" Fib = n\n"
" ELSE\n"
" Fib = Fib(n - 1) + Fib(n - 2)\n"
" END IF\n"
"End Function\n"
"\n"
"PRINT Fib(0); Fib(1); Fib(5); Fib(10)\n"
);
// ============================================================
// Coverage: String comparison operators
// ============================================================
runProgram("String comparison ops",
"IF \"abc\" < \"def\" THEN PRINT \"lt ok\"\n"
"IF \"abc\" <= \"abc\" THEN PRINT \"le ok\"\n"
"IF \"def\" > \"abc\" THEN PRINT \"gt ok\"\n"
"IF \"abc\" >= \"abc\" THEN PRINT \"ge ok\"\n"
"IF \"abc\" <> \"def\" THEN PRINT \"ne ok\"\n"
"IF \"abc\" = \"abc\" THEN PRINT \"eq ok\"\n"
);
// ============================================================
// Coverage: LOAD/UNLOAD/Me statements compile
// ============================================================
{
printf("=== Form statements compile ===\n");
const char *src =
"Sub Form1_Load ()\n"
" Me.Show\n"
" Me.Hide\n"
" Load frmOther\n"
" Unload frmOther\n"
"End Sub\n";
int32_t len = (int32_t)strlen(src);
BasParserT parser;
basParserInit(&parser, src, len);
if (!basParse(&parser)) {
printf("COMPILE ERROR: %s\n", parser.error);
basParserFree(&parser);
} else {
BasModuleT *mod = basParserBuildModule(&parser);
basParserFree(&parser);
if (mod) {
printf("OK: %d procs, %d bytes\n", (int)mod->procCount, (int)mod->codeLen);
basModuleFree(mod);
}
}
printf("\n");
}
// ============================================================
// Coverage: Me.Property assignment compiles
// ============================================================
{
printf("=== Me.Property assignment ===\n");
const char *src =
"Sub Form1_Load ()\n"
" Me.Caption = \"Hello\"\n"
" Me.Visible = 1\n"
"End Sub\n";
int32_t len = (int32_t)strlen(src);
BasParserT parser;
basParserInit(&parser, src, len);
if (!basParse(&parser)) {
printf("COMPILE ERROR: %s\n", parser.error);
basParserFree(&parser);
} else {
BasModuleT *mod = basParserBuildModule(&parser);
basParserFree(&parser);
if (mod) {
printf("OK\n");
basModuleFree(mod);
}
}
printf("\n");
}
// ============================================================
// Coverage: Me.Control.Property compiles
// ============================================================
{
printf("=== Me.Control.Property ===\n");
const char *src =
"Sub Form1_Load ()\n"
" Me.Text1.Text = \"hello\"\n"
" Me.Label1.Caption = \"world\"\n"
"End Sub\n";
int32_t len = (int32_t)strlen(src);
BasParserT parser;
basParserInit(&parser, src, len);
if (!basParse(&parser)) {
printf("COMPILE ERROR: %s\n", parser.error);
basParserFree(&parser);
} else {
BasModuleT *mod = basParserBuildModule(&parser);
basParserFree(&parser);
if (mod) {
printf("OK\n");
basModuleFree(mod);
}
}
printf("\n");
}
// ============================================================
// Coverage: DOEVENTS compiles
// ============================================================
{
printf("=== DOEVENTS compiles ===\n");
const char *src =
"Sub Form1_Load ()\n"
" DoEvents\n"
"End Sub\n";
int32_t len = (int32_t)strlen(src);
BasParserT parser;
basParserInit(&parser, src, len);
if (!basParse(&parser)) {
printf("COMPILE ERROR: %s\n", parser.error);
basParserFree(&parser);
} else {
BasModuleT *mod = basParserBuildModule(&parser);
basParserFree(&parser);
if (mod) {
printf("OK\n");
basModuleFree(mod);
}
}
printf("\n");
}
// ============================================================
// Coverage: Bare sub call (no CALL keyword, no parens)
// ============================================================
runProgram("Bare sub call",
"Sub Greet ()\n"
" PRINT \"hello\"\n"
"End Sub\n"
"\n"
"Greet\n"
);
// ============================================================
// Coverage: Bare sub call with forward reference
// ============================================================
runProgram("Bare sub call forward ref",
"DoWork\n"
"\n"
"Sub DoWork ()\n"
" PRINT \"worked\"\n"
"End Sub\n"
);
// ============================================================
// Coverage: Unresolved forward reference error
// ============================================================
{
printf("=== Unresolved forward reference ===\n");
const char *src = "NeverDefined\n";
int32_t len = (int32_t)strlen(src);
BasParserT parser;
basParserInit(&parser, src, len);
if (!basParse(&parser)) {
printf("Correctly caught: %s\n", parser.error);
} else {
printf("ERROR: should have failed\n");
}
basParserFree(&parser);
printf("\n");
}
// ============================================================
// Coverage: END statement terminates (distinct from HALT)
// ============================================================
runProgram("END statement",
"PRINT \"before\"\n"
"END\n"
"PRINT \"after\"\n"
);
// ============================================================
// Coverage: Nested UDT field store
// ============================================================
runProgram("Nested UDT store and load",
"TYPE InnerT\n"
" val AS INTEGER\n"
"END TYPE\n"
"\n"
"TYPE OuterT\n"
" child AS InnerT\n"
" name AS STRING\n"
"END TYPE\n"
"\n"
"DIM o AS OuterT\n"
"o.name = \"test\"\n"
"o.child.val = 42\n"
"PRINT o.name\n"
"PRINT o.child.val\n"
);
// ============================================================
// Coverage: Array of UDT field store
// ============================================================
runProgram("Array of UDT field store",
"TYPE PointT\n"
" x AS INTEGER\n"
" y AS INTEGER\n"
"END TYPE\n"
"\n"
"DIM pts(5) AS PointT\n"
"pts(1).x = 10\n"
"pts(1).y = 20\n"
"pts(3).x = 30\n"
"pts(3).y = 40\n"
"PRINT pts(1).x; pts(1).y\n"
"PRINT pts(3).x; pts(3).y\n"
);
// ============================================================
// Coverage: VB operator precedence (^ binds tighter than unary -)
// ============================================================
runProgram("Exponent precedence",
"PRINT -2 ^ 2\n" // -(2^2) = -4
"PRINT (-2) ^ 2\n" // (-2)^2 = 4
"PRINT 3 ^ 2 + 1\n" // 9 + 1 = 10
);
// ============================================================
// Coverage: Me.Show compiles in a Sub
// ============================================================
{
printf("=== Me.Show in Sub ===\n");
const char *src =
"Sub Form1_Load ()\n"
" Me.Show\n"
" Me.Caption = \"Hello\"\n"
" Me.Hide\n"
"End Sub\n";
int32_t len = (int32_t)strlen(src);
BasParserT parser;
basParserInit(&parser, src, len);
if (!basParse(&parser)) {
printf("COMPILE ERROR: %s\n", parser.error);
} else {
printf("OK\n");
}
basParserFree(&parser);
printf("\n");
}
// ============================================================
// Coverage: OPTION EXPLICIT with valid declaration
// ============================================================
runProgram("OPTION EXPLICIT valid",
"OPTION EXPLICIT\n"
"DIM x AS INTEGER\n"
"x = 42\n"
"PRINT x\n"
);
// ============================================================
// Coverage: STATIC variable retains value
// ============================================================
runProgram("STATIC in sub",
"Sub Counter ()\n"
" STATIC n AS INTEGER\n"
" n = n + 1\n"
" PRINT n\n"
"End Sub\n"
"\n"
"Counter\n"
"Counter\n"
"Counter\n"
);
// ============================================================
// Coverage: MsgBox statement form (no return value)
// ============================================================
runProgram("MsgBox statement",
"MsgBox \"Hello\"\n"
);
// ============================================================
// Coverage: MsgBox statement with flags
// ============================================================
runProgram("MsgBox statement with flags",
"MsgBox \"Save?\", vbYesNo + vbQuestion\n"
);
// ============================================================
// Coverage: MsgBox function form (returns value)
// ============================================================
runProgram("MsgBox function",
"DIM result AS INTEGER\n"
"result = MsgBox(\"Continue?\", vbYesNo)\n"
"PRINT result\n"
);
// ============================================================
// Coverage: MsgBox function with default flags
// ============================================================
runProgram("MsgBox function default flags",
"DIM r AS INTEGER\n"
"r = MsgBox(\"OK\")\n"
"PRINT r\n"
);
// ============================================================
// Coverage: VB3 predefined constants
// ============================================================
runProgram("VB3 predefined constants",
"PRINT vbOKOnly\n"
"PRINT vbOKCancel\n"
"PRINT vbYesNo\n"
"PRINT vbYesNoCancel\n"
"PRINT vbRetryCancel\n"
"PRINT vbInformation\n"
"PRINT vbExclamation\n"
"PRINT vbCritical\n"
"PRINT vbQuestion\n"
"PRINT vbOK\n"
"PRINT vbCancel\n"
"PRINT vbYes\n"
"PRINT vbNo\n"
"PRINT vbRetry\n"
);
// ============================================================
// Coverage: MsgBox result used in If
// ============================================================
runProgram("MsgBox in If condition",
"If MsgBox(\"Exit?\", vbYesNo) = vbYes Then\n"
" PRINT \"yes\"\n"
"End If\n"
);
// ============================================================
// Coverage: Control array read -- Name(0).Caption
// ============================================================
{
printf("=== Control array read ===\n");
BasParserT parser;
basParserInit(&parser,
"DIM x AS STRING\n"
"x = Command1(0).Caption\n"
"PRINT x\n",
-1);
if (!basParse(&parser)) {
printf("COMPILE ERROR: %s\n", parser.error);
} else {
printf("OK\n");
}
basParserFree(&parser);
printf("\n");
}
// ============================================================
// Coverage: Control array write -- Name(idx).Caption = "text"
// ============================================================
{
printf("=== Control array write ===\n");
BasParserT parser;
basParserInit(&parser,
"DIM idx AS INTEGER\n"
"idx = 1\n"
"Command1(idx).Caption = \"Hello\"\n",
-1);
if (!basParse(&parser)) {
printf("COMPILE ERROR: %s\n", parser.error);
} else {
printf("OK\n");
}
basParserFree(&parser);
printf("\n");
}
// ============================================================
// Coverage: Control array complex index -- Name(X + 1).Caption
// ============================================================
{
printf("=== Control array complex index ===\n");
BasParserT parser;
basParserInit(&parser,
"DIM i AS INTEGER\n"
"DIM s AS STRING\n"
"i = 2\n"
"s = Btn(i + 1).Text\n"
"PRINT s\n",
-1);
if (!basParse(&parser)) {
printf("COMPILE ERROR: %s\n", parser.error);
} else {
printf("OK\n");
}
basParserFree(&parser);
printf("\n");
}
// ============================================================
// Coverage: Forward-ref function still works (no dot after paren)
// ============================================================
runProgram("Forward-ref function still works",
"Function GetValue () As Integer\n"
" GetValue = 42\n"
"End Function\n"
"\n"
"PRINT GetValue()\n"
);
// ============================================================
// Coverage: Control array in event handler
// ============================================================
{
printf("=== Control array event handler ===\n");
BasParserT parser;
basParserInit(&parser,
"Sub Cmd_Click (Index As Integer)\n"
" Cmd(Index).Caption = \"Clicked\"\n"
"End Sub\n",
-1);
if (!basParse(&parser)) {
printf("COMPILE ERROR: %s\n", parser.error);
} else {
printf("OK\n");
}
basParserFree(&parser);
printf("\n");
}
// ============================================================
// Coverage: Me.Control(idx).Property
// ============================================================
{
printf("=== Me.Control(idx).Property ===\n");
BasParserT parser;
basParserInit(&parser,
"Sub Cmd_Click (Index As Integer)\n"
" Me.Cmd(Index).Caption = \"OK\"\n"
"End Sub\n",
-1);
if (!basParse(&parser)) {
printf("COMPILE ERROR: %s\n", parser.error);
} else {
printf("OK\n");
}
basParserFree(&parser);
printf("\n");
}
// ============================================================
// Coverage: BEGINFORM/ENDFORM with form-level DIM
// ============================================================
{
printf("=== Form-level variables ===\n");
BasParserT parser;
basParserInit(&parser,
"BEGINFORM \"Form1\"\n"
"Dim counter As Integer\n"
"Dim msg As String\n"
"\n"
"Sub Form1_Load ()\n"
" counter = 0\n"
" msg = \"hello\"\n"
"End Sub\n"
"\n"
"Sub Timer1_Timer ()\n"
" counter = counter + 1\n"
"End Sub\n"
"ENDFORM\n",
-1);
if (!basParse(&parser)) {
printf("COMPILE ERROR: %s\n", parser.error);
} else {
printf("OK\n");
}
basParserFree(&parser);
printf("\n");
}
// ============================================================
// Coverage: Form-scope arrays compile (init deferred to load)
// ============================================================
{
printf("=== Form-scope array ===\n");
BasParserT parser;
basParserInit(&parser,
"BEGINFORM \"Form1\"\n"
"Dim arr(10) As Integer\n"
"\n"
"Sub Form1_Load ()\n"
" arr(0) = 42\n"
"End Sub\n"
"ENDFORM\n",
-1);
if (!basParse(&parser)) {
printf("COMPILE ERROR: %s\n", parser.error);
} else {
printf("OK\n");
}
basParserFree(&parser);
printf("\n");
}
// ============================================================
// Coverage: Two forms with same-named form vars (independent)
// ============================================================
{
printf("=== Two forms same var name ===\n");
BasParserT parser;
basParserInit(&parser,
"BEGINFORM \"Form1\"\n"
"Dim counter As Integer\n"
"Sub Form1_Load ()\n"
" counter = 1\n"
"End Sub\n"
"ENDFORM\n"
"\n"
"BEGINFORM \"Form2\"\n"
"Dim counter As Integer\n"
"Sub Form2_Load ()\n"
" counter = 2\n"
"End Sub\n"
"ENDFORM\n",
-1);
if (!basParse(&parser)) {
printf("COMPILE ERROR: %s\n", parser.error);
} else {
printf("OK\n");
}
basParserFree(&parser);
printf("\n");
}
// ============================================================
// Coverage: Nested BEGINFORM rejected
// ============================================================
{
printf("=== Nested BEGINFORM rejected ===\n");
BasParserT parser;
basParserInit(&parser,
"BEGINFORM \"Form1\"\n"
"BEGINFORM \"Form2\"\n"
"ENDFORM\n"
"ENDFORM\n",
-1);
if (!basParse(&parser)) {
printf("OK (expected error: %s)\n", parser.error);
} else {
printf("FAIL: should have rejected nested BEGINFORM\n");
}
basParserFree(&parser);
printf("\n");
}
printf("All tests complete.\n");
return 0;
}