// test_compiler.c -- End-to-end test: source -> compiler -> VM -> output // // Build (native): // gcc -O2 -Wall -o test_compiler test_compiler.c \ // compiler/lexer.c compiler/parser.c compiler/codegen.c compiler/symtab.c \ // runtime/vm.c runtime/values.c -lm #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"); } printf("All tests complete.\n"); return 0; }