// 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 #include 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"); } // 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 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: 123 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"); } printf("All tests complete.\n"); return 0; }