// 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 // ============================================================ runProgram("Me keyword", "' Me compiles to OP_ME_REF (returns NULL outside form context)\n" "PRINT \"ok\"\n" ); // Expected: ok (just verify Me doesn't crash compilation) printf("All tests complete.\n"); return 0; }