DVX_GUI/dvxbasic/test_compiler.c

850 lines
21 KiB
C

// 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 <stdio.h>
#include <string.h>
static void runProgram(const char *name, const char *source) {
printf("=== %s ===\n", name);
int32_t len = (int32_t)strlen(source);
BasParserT parser;
basParserInit(&parser, source, len);
if (!basParse(&parser)) {
printf("COMPILE ERROR: %s\n\n", parser.error);
basParserFree(&parser);
return;
}
BasModuleT *mod = basParserBuildModule(&parser);
basParserFree(&parser);
if (!mod) {
printf("MODULE BUILD FAILED\n\n");
return;
}
BasVmT *vm = basVmCreate();
basVmLoadModule(vm, mod);
// Module-level code uses callStack[0] as implicit main frame
vm->callStack[0].localCount = mod->globalCount > 64 ? 64 : mod->globalCount;
vm->callDepth = 1;
BasVmResultE result = basVmRun(vm);
if (result != BAS_VM_HALTED && result != BAS_VM_OK) {
printf("[VM error %d: %s]\n", result, basVmGetError(vm));
}
basVmDestroy(vm);
basModuleFree(mod);
printf("\n");
}
int main(void) {
printf("DVX BASIC Compiler Tests\n");
printf("========================\n\n");
basStringSystemInit();
// Test 1: Hello World
runProgram("Hello World",
"PRINT \"Hello, World!\"\n"
);
// Test 2: Arithmetic
runProgram("Arithmetic",
"PRINT 2 + 3 * 4\n"
"PRINT 10 \\ 3\n"
"PRINT 10 MOD 3\n"
"PRINT 2 ^ 8\n"
);
// Test 3: String operations
runProgram("String Ops",
"DIM s AS STRING\n"
"s = \"Hello, BASIC!\"\n"
"PRINT s\n"
"PRINT LEN(s)\n"
"PRINT LEFT$(s, 5)\n"
"PRINT RIGHT$(s, 6)\n"
"PRINT MID$(s, 8, 5)\n"
"PRINT UCASE$(s)\n"
);
// Test 4: IF/THEN/ELSE
runProgram("IF/THEN/ELSE",
"DIM x AS INTEGER\n"
"x = 42\n"
"IF x > 100 THEN\n"
" PRINT \"big\"\n"
"ELSEIF x > 10 THEN\n"
" PRINT \"medium\"\n"
"ELSE\n"
" PRINT \"small\"\n"
"END IF\n"
);
// Test 5: FOR loop
runProgram("FOR Loop",
"DIM i AS INTEGER\n"
"FOR i = 1 TO 10\n"
" PRINT i;\n"
"NEXT i\n"
"PRINT\n"
);
// Test 6: DO/WHILE loop
runProgram("DO/WHILE Loop",
"DIM n AS INTEGER\n"
"n = 1\n"
"DO WHILE n <= 5\n"
" PRINT n;\n"
" n = n + 1\n"
"LOOP\n"
"PRINT\n"
);
// Test 7: SUB and FUNCTION
runProgram("SUB and FUNCTION",
"DECLARE SUB Greet(name AS STRING)\n"
"DECLARE FUNCTION Square(x AS INTEGER) AS INTEGER\n"
"\n"
"CALL Greet(\"World\")\n"
"PRINT Square(7)\n"
"\n"
"SUB Greet(name AS STRING)\n"
" PRINT \"Hello, \" & name & \"!\"\n"
"END SUB\n"
"\n"
"FUNCTION Square(x AS INTEGER) AS INTEGER\n"
" Square = x * x\n"
"END FUNCTION\n"
);
// Test 8: SELECT CASE
runProgram("SELECT CASE",
"DIM grade AS STRING\n"
"grade = \"B\"\n"
"SELECT CASE grade\n"
" CASE \"A\"\n"
" PRINT \"Excellent\"\n"
" CASE \"B\", \"C\"\n"
" PRINT \"Good\"\n"
" CASE ELSE\n"
" PRINT \"Other\"\n"
"END SELECT\n"
);
// Test 9: Fibonacci
runProgram("Fibonacci",
"DIM a AS INTEGER\n"
"DIM b AS INTEGER\n"
"DIM temp AS INTEGER\n"
"DIM i AS INTEGER\n"
"a = 0\n"
"b = 1\n"
"FOR i = 1 TO 10\n"
" PRINT a;\n"
" temp = a + b\n"
" a = b\n"
" b = temp\n"
"NEXT i\n"
"PRINT\n"
);
// Test 10: Math functions
runProgram("Math Functions",
"PRINT ABS(-42)\n"
"PRINT SQR(144)\n"
"PRINT INT(3.7)\n"
);
// Test 11: File I/O
runProgram("File I/O",
"OPEN \"/tmp/dvxbasic_test.txt\" FOR OUTPUT AS #1\n"
"PRINT #1, \"Hello from BASIC!\"\n"
"PRINT #1, \"Line two\"\n"
"PRINT #1, \"42\"\n"
"CLOSE #1\n"
"\n"
"DIM line$ AS STRING\n"
"DIM count AS INTEGER\n"
"count = 0\n"
"OPEN \"/tmp/dvxbasic_test.txt\" FOR INPUT AS #1\n"
"DO WHILE NOT EOF(#1)\n"
" INPUT #1, line$\n"
" PRINT line$\n"
" count = count + 1\n"
"LOOP\n"
"CLOSE #1\n"
"PRINT count;\n"
"PRINT \"lines read\"\n"
);
// Test 12: LINE INPUT# and APPEND
runProgram("LINE INPUT and APPEND",
"OPEN \"/tmp/dvxbasic_test2.txt\" FOR OUTPUT AS #2\n"
"PRINT #2, \"First line\"\n"
"CLOSE #2\n"
"\n"
"OPEN \"/tmp/dvxbasic_test2.txt\" FOR APPEND AS #2\n"
"PRINT #2, \"Appended line\"\n"
"CLOSE #2\n"
"\n"
"DIM s$ AS STRING\n"
"OPEN \"/tmp/dvxbasic_test2.txt\" FOR INPUT AS #2\n"
"LINE INPUT #2, s$\n"
"PRINT s$\n"
"LINE INPUT #2, s$\n"
"PRINT s$\n"
"CLOSE #2\n"
);
// Test 13: Array -- 1D with default lbound=0
runProgram("1D Array",
"DIM arr(5) AS INTEGER\n"
"DIM i AS INTEGER\n"
"FOR i = 1 TO 5\n"
" arr(i) = i * i\n"
"NEXT i\n"
"FOR i = 1 TO 5\n"
" PRINT arr(i);\n"
"NEXT i\n"
"PRINT\n"
);
// Expected: 1 4 9 16 25
// Test 14: Multi-dimensional array
runProgram("Multi-dim Array",
"DIM m(2, 2) AS INTEGER\n"
"m(1, 1) = 11\n"
"m(1, 2) = 12\n"
"m(2, 1) = 21\n"
"m(2, 2) = 22\n"
"PRINT m(1, 1); m(1, 2); m(2, 1); m(2, 2)\n"
);
// Expected: 11 12 21 22
// Test 15: Array with explicit bounds (TO syntax)
runProgram("Array with TO bounds",
"DIM a(1 TO 3) AS INTEGER\n"
"a(1) = 10\n"
"a(2) = 20\n"
"a(3) = 30\n"
"PRINT a(1); a(2); a(3)\n"
);
// Expected: 10 20 30
// Test 16: LBOUND and UBOUND
runProgram("LBOUND/UBOUND",
"DIM a(5 TO 10) AS INTEGER\n"
"PRINT LBOUND(a); UBOUND(a)\n"
);
// Expected: 5 10
// Test 17: User-defined TYPE
runProgram("TYPE",
"TYPE Point\n"
" x AS INTEGER\n"
" y AS INTEGER\n"
"END TYPE\n"
"DIM p AS Point\n"
"p.x = 10\n"
"p.y = 20\n"
"PRINT p.x; p.y\n"
);
// Expected: 10 20
// Test 18: String array
runProgram("String Array",
"DIM names(3) AS STRING\n"
"names(0) = \"Alice\"\n"
"names(1) = \"Bob\"\n"
"names(2) = \"Charlie\"\n"
"DIM i AS INTEGER\n"
"FOR i = 0 TO 2\n"
" PRINT names(i)\n"
"NEXT i\n"
);
// Expected: Alice / Bob / Charlie
// Test 19: REDIM with PRESERVE
runProgram("REDIM PRESERVE",
"DIM a(3) AS INTEGER\n"
"a(0) = 100\n"
"a(1) = 200\n"
"a(2) = 300\n"
"REDIM PRESERVE a(5) AS INTEGER\n"
"a(4) = 500\n"
"PRINT a(0); a(1); a(2); a(4)\n"
);
// Expected: 100 200 300 500
// Test 20: ERASE
runProgram("ERASE",
"DIM a(3) AS INTEGER\n"
"a(1) = 42\n"
"ERASE a\n"
"DIM b(2) AS INTEGER\n"
"b(1) = 99\n"
"PRINT b(1)\n"
);
// Expected: 99
// Test 21: Array in FOR loop accumulation
runProgram("Array Accumulation",
"DIM sums(5) AS INTEGER\n"
"DIM i AS INTEGER\n"
"DIM j AS INTEGER\n"
"FOR i = 1 TO 5\n"
" sums(i) = 0\n"
" FOR j = 1 TO i\n"
" sums(i) = sums(i) + j\n"
" NEXT j\n"
"NEXT i\n"
"FOR i = 1 TO 5\n"
" PRINT sums(i);\n"
"NEXT i\n"
"PRINT\n"
);
// Expected: 1 3 6 10 15
// ============================================================
// Batch 1: Control Flow
// ============================================================
// Test: GOTO with forward jump
runProgram("GOTO Forward",
"PRINT \"before\"\n"
"GOTO skip\n"
"PRINT \"skipped\"\n"
"skip:\n"
"PRINT \"after\"\n"
);
// Expected: before / after
// Test: GOTO with backward jump
runProgram("GOTO Backward",
"DIM n AS INTEGER\n"
"n = 0\n"
"top:\n"
"n = n + 1\n"
"IF n < 5 THEN GOTO top\n"
"PRINT n\n"
);
// Expected: 5
// Test: GOSUB/RETURN
runProgram("GOSUB/RETURN",
"DIM x AS INTEGER\n"
"x = 10\n"
"GOSUB dbl\n"
"PRINT x\n"
"END\n"
"dbl:\n"
"x = x * 2\n"
"RETURN\n"
);
// Expected: 20
// Test: ON ERROR GOTO -- verify error handler catches errors
// and ERR returns the error number
runProgram("ON ERROR GOTO",
"ON ERROR GOTO handler\n"
"PRINT 10 / 0\n"
"END\n"
"handler:\n"
"PRINT \"caught\"\n"
"PRINT ERR\n"
);
// Expected: caught / 11
// Test: Single-line IF
runProgram("Single-line IF",
"DIM x AS INTEGER\n"
"x = 42\n"
"IF x > 10 THEN PRINT \"big\"\n"
"IF x < 10 THEN PRINT \"small\"\n"
"IF x = 42 THEN PRINT \"exact\" ELSE PRINT \"nope\"\n"
);
// Expected: big / exact
// Test: Multi-statement line with :
runProgram("Multi-statement :",
"DIM x AS INTEGER\n"
"DIM y AS INTEGER\n"
"x = 1 : y = 2 : PRINT x + y\n"
);
// Expected: 3
// ============================================================
// Batch 2: Misc Features
// ============================================================
// Test: SWAP
runProgram("SWAP",
"DIM a AS INTEGER\n"
"DIM b AS INTEGER\n"
"a = 10\n"
"b = 20\n"
"SWAP a, b\n"
"PRINT a;\n"
"PRINT b\n"
);
// Expected: 20 10
// Test: TIMER (returns number > 0)
runProgram("TIMER",
"DIM t AS DOUBLE\n"
"t = TIMER\n"
"IF t > 0 THEN PRINT \"ok\"\n"
);
// Expected: ok
// Test: DATE$ (returns non-empty string)
runProgram("DATE$",
"DIM d$ AS STRING\n"
"d$ = DATE$\n"
"IF LEN(d$) > 0 THEN PRINT \"ok\"\n"
);
// Expected: ok
// Test: TIME$ (returns non-empty string)
runProgram("TIME$",
"DIM t$ AS STRING\n"
"t$ = TIME$\n"
"IF LEN(t$) > 0 THEN PRINT \"ok\"\n"
);
// Expected: ok
// Test: ENVIRON$
runProgram("ENVIRON$",
"DIM p$ AS STRING\n"
"p$ = ENVIRON$(\"HOME\")\n"
"IF LEN(p$) > 0 THEN PRINT \"ok\"\n"
);
// Expected: ok
// ============================================================
// Batch 3: New features (DATA/READ/RESTORE, DIM SHARED,
// STATIC, DEF FN, OPTION BASE)
// ============================================================
// Test: DATA/READ/RESTORE
runProgram("DATA/READ/RESTORE",
"DATA 10, 20, \"hello\"\n"
"DIM a AS INTEGER\n"
"DIM b AS INTEGER\n"
"DIM c AS STRING\n"
"READ a, b, c\n"
"PRINT a; b;\n"
"PRINT c\n"
"RESTORE\n"
"READ a\n"
"PRINT a\n"
);
// Expected: 10 20 hello / 10
// Test: DIM SHARED
runProgram("DIM SHARED",
"DIM SHARED count AS INTEGER\n"
"count = 0\n"
"CALL Increment\n"
"CALL Increment\n"
"CALL Increment\n"
"PRINT count\n"
"SUB Increment\n"
" count = count + 1\n"
"END SUB\n"
);
// Expected: 3
// Test: STATIC
runProgram("STATIC",
"CALL Counter\n"
"CALL Counter\n"
"CALL Counter\n"
"SUB Counter\n"
" STATIC n AS INTEGER\n"
" n = n + 1\n"
" PRINT n;\n"
"END SUB\n"
"PRINT\n"
);
// Expected: 1 2 3
// Test: DEF FN
runProgram("DEF FN",
"DEF FNdouble(x AS INTEGER) = x * 2\n"
"PRINT FNdouble(5)\n"
"PRINT FNdouble(21)\n"
);
// Expected: 10 / 42
// Test: OPTION BASE
runProgram("OPTION BASE",
"OPTION BASE 1\n"
"DIM arr(3) AS INTEGER\n"
"arr(1) = 10\n"
"arr(3) = 30\n"
"PRINT arr(1); arr(3)\n"
);
// Expected: 10 30
// Test: DATA with mixed types
runProgram("DATA mixed types",
"DATA 100, 3.14, \"world\"\n"
"DIM x AS INTEGER\n"
"DIM y AS DOUBLE\n"
"DIM z AS STRING\n"
"READ x, y, z\n"
"PRINT x\n"
"PRINT z\n"
);
// Expected: 100 / world
// Test: Multiple DATA statements scattered
runProgram("DATA scattered",
"DIM a AS INTEGER\n"
"DIM b AS INTEGER\n"
"DIM c AS INTEGER\n"
"DATA 1, 2\n"
"READ a, b\n"
"DATA 3\n"
"READ c\n"
"PRINT a; b; c\n"
);
// Expected: 1 2 3
// Test: DIM SHARED with SUB modifying shared variable
runProgram("DIM SHARED multi",
"DIM SHARED total AS INTEGER\n"
"DIM SHARED msg AS STRING\n"
"total = 100\n"
"msg = \"start\"\n"
"CALL Modify\n"
"PRINT total\n"
"PRINT msg\n"
"SUB Modify\n"
" total = total + 50\n"
" msg = \"done\"\n"
"END SUB\n"
);
// Expected: 150 / done
// ============================================================
// Batch 4: New I/O and string features
// ============================================================
// Test: WRITE #
runProgram("WRITE #",
"OPEN \"/tmp/dvxbasic_write.txt\" FOR OUTPUT AS #1\n"
"WRITE #1, 10, \"hello\", 3.14\n"
"CLOSE #1\n"
"OPEN \"/tmp/dvxbasic_write.txt\" FOR INPUT AS #1\n"
"DIM s AS STRING\n"
"LINE INPUT #1, s\n"
"PRINT s\n"
"CLOSE #1\n"
);
// Expected: 10,"hello",3.14
// Test: FREEFILE
runProgram("FREEFILE",
"DIM f AS INTEGER\n"
"f = FREEFILE\n"
"PRINT f\n"
);
// Expected: 1
// Test: PRINT USING numeric
runProgram("PRINT USING numeric",
"PRINT USING \"###.##\"; 3.14159\n"
);
// Expected: 3.14
// Test: PRINT USING string
runProgram("PRINT USING string",
"PRINT USING \"!\"; \"Hello\"\n"
);
// Expected: H
// Test: SPC and TAB in PRINT
runProgram("SPC/TAB",
"PRINT SPC(3); \"hi\"\n"
);
// Expected: hi
// Test: Fixed-length string
runProgram("STRING * n",
"DIM s AS STRING * 5\n"
"s = \"Hi\"\n"
"PRINT \"[\" & s & \"]\"\n"
"PRINT LEN(s)\n"
);
// Expected: [Hi ] / 5
// Test: MID$ statement
runProgram("MID$ statement",
"DIM s AS STRING\n"
"s = \"Hello World\"\n"
"MID$(s, 7, 5) = \"BASIC\"\n"
"PRINT s\n"
);
// Expected: Hello BASIC
// Test: OPEN FOR BINARY / GET / PUT
runProgram("BINARY GET/PUT",
"DIM v AS INTEGER\n"
"OPEN \"/tmp/dvxbasic_bin.tmp\" FOR BINARY AS #1\n"
"v = 12345\n"
"PUT #1, , v\n"
"SEEK #1, 1\n"
"DIM r AS INTEGER\n"
"GET #1, , r\n"
"PRINT r\n"
"CLOSE #1\n"
);
// Expected: 12345
// Test: LOF and LOC
runProgram("LOF/LOC",
"OPEN \"/tmp/dvxbasic_lof.txt\" FOR OUTPUT AS #1\n"
"PRINT #1, \"test\"\n"
"CLOSE #1\n"
"OPEN \"/tmp/dvxbasic_lof.txt\" FOR INPUT AS #1\n"
"DIM sz AS LONG\n"
"sz = LOF(1)\n"
"IF sz > 0 THEN PRINT \"ok\"\n"
"CLOSE #1\n"
);
// Expected: ok
// Test: INPUT$(n, #channel)
runProgram("INPUT$",
"OPEN \"/tmp/dvxbasic_inp.txt\" FOR OUTPUT AS #1\n"
"PRINT #1, \"ABCDEF\"\n"
"CLOSE #1\n"
"OPEN \"/tmp/dvxbasic_inp.txt\" FOR INPUT AS #1\n"
"DIM s AS STRING\n"
"s = INPUT$(3, #1)\n"
"PRINT s\n"
"CLOSE #1\n"
);
// Expected: ABC
// Test: SEEK function form
runProgram("SEEK function",
"OPEN \"/tmp/dvxbasic_seek.txt\" FOR OUTPUT AS #1\n"
"PRINT #1, \"test\"\n"
"CLOSE #1\n"
"OPEN \"/tmp/dvxbasic_seek.txt\" FOR BINARY AS #1\n"
"DIM p AS LONG\n"
"p = SEEK(1)\n"
"IF p = 1 THEN PRINT \"ok\"\n"
"CLOSE #1\n"
);
// Expected: ok
// Test: ON n GOTO
runProgram("ON n GOTO",
"DIM n AS INTEGER\n"
"n = 2\n"
"ON n GOTO ten, twenty, thirty\n"
"PRINT \"none\"\n"
"GOTO done\n"
"ten:\n"
"PRINT \"ten\"\n"
"GOTO done\n"
"twenty:\n"
"PRINT \"twenty\"\n"
"GOTO done\n"
"thirty:\n"
"PRINT \"thirty\"\n"
"done:\n"
);
// Expected: twenty
// Test: ON n GOTO (no match)
runProgram("ON n GOTO no match",
"DIM n AS INTEGER\n"
"n = 5\n"
"ON n GOTO aa, bb\n"
"PRINT \"fallthrough\"\n"
"GOTO done2\n"
"aa:\n"
"PRINT \"aa\"\n"
"GOTO done2\n"
"bb:\n"
"PRINT \"bb\"\n"
"done2:\n"
);
// Expected: fallthrough
// Test: ON n GOSUB
runProgram("ON n GOSUB",
"DIM n AS INTEGER\n"
"DIM result AS INTEGER\n"
"result = 0\n"
"n = 2\n"
"ON n GOSUB addTen, addTwenty, addThirty\n"
"PRINT result\n"
"GOTO endProg\n"
"addTen:\n"
"result = result + 10\n"
"RETURN\n"
"addTwenty:\n"
"result = result + 20\n"
"RETURN\n"
"addThirty:\n"
"result = result + 30\n"
"RETURN\n"
"endProg:\n"
);
// Expected: 20
// Test: FORMAT$
runProgram("FORMAT$",
"PRINT FORMAT$(1234.5, \"#,##0.00\")\n"
"PRINT FORMAT$(0.5, \"0.00\")\n"
"PRINT FORMAT$(-42, \"+#0\")\n"
"PRINT FORMAT$(0.75, \"percent\")\n"
);
// Expected: 1,234.50\n0.50\n-42\n75%
// Test: SHELL as function expression
runProgram("SHELL function",
"DIM r AS INTEGER\n"
"r = SHELL(\"echo hello > /dev/null\")\n"
"IF r = 0 THEN PRINT \"ok\"\n"
);
// Expected: ok
// Test: SHELL as statement
runProgram("SHELL statement",
"SHELL \"echo hello > /dev/null\"\n"
"PRINT \"done\"\n"
);
// Expected: done
// Test: OPTION COMPARE TEXT
runProgram("OPTION COMPARE TEXT",
"OPTION COMPARE TEXT\n"
"IF \"hello\" = \"HELLO\" THEN\n"
" PRINT \"equal\"\n"
"ELSE\n"
" PRINT \"not equal\"\n"
"END IF\n"
"IF \"abc\" < \"XYZ\" THEN\n"
" PRINT \"less\"\n"
"END IF\n"
);
// Expected: equal\nless
// Test: OPTION COMPARE BINARY (default)
runProgram("OPTION COMPARE BINARY",
"OPTION COMPARE BINARY\n"
"IF \"hello\" = \"HELLO\" THEN\n"
" PRINT \"equal\"\n"
"ELSE\n"
" PRINT \"not equal\"\n"
"END IF\n"
);
// Expected: not equal
// Test: EQV operator
runProgram("EQV operator",
"PRINT -1 EQV -1\n"
"PRINT 0 EQV 0\n"
"PRINT -1 EQV 0\n"
"PRINT 0 EQV -1\n"
);
// Expected: -1\n-1\n0\n0
// Test: IMP operator
runProgram("IMP operator",
"PRINT 0 IMP -1\n"
"PRINT -1 IMP 0\n"
"PRINT -1 IMP -1\n"
"PRINT 0 IMP 0\n"
);
// Expected: -1\n0\n-1\n-1
// Test: PRINT USING advanced patterns
runProgram("PRINT USING advanced",
"PRINT USING \"**#,##0.00\"; 1234.5\n"
"PRINT USING \"$$#,##0.00\"; 42.5\n"
"PRINT USING \"+###.##\"; 42.5\n"
"PRINT USING \"+###.##\"; -42.5\n"
"PRINT USING \"###.##-\"; -42.5\n"
"PRINT USING \"###.##-\"; 42.5\n"
"PRINT USING \"#.##^^^^\"; 1234.5\n"
);
// Test: DEFINT
runProgram("DEFINT",
"DEFINT A-Z\n"
"a = 42\n"
"b = 3.7\n"
"PRINT a; b\n"
);
// Test: DEFSTR
runProgram("DEFSTR",
"DEFSTR S\n"
"s = \"hello\"\n"
"PRINT s\n"
);
// Test: DEFINT range
runProgram("DEFINT range",
"DEFINT I-N\n"
"i = 10\n"
"j = 20\n"
"x = 3.14\n"
"PRINT i; j; x\n"
);
// Test: OPTION EXPLICIT success
runProgram("OPTION EXPLICIT ok",
"OPTION EXPLICIT\n"
"DIM x AS INTEGER\n"
"x = 42\n"
"PRINT x\n"
);
// Test: OPTION EXPLICIT failure (should error)
{
printf("=== OPTION EXPLICIT error ===\n");
const char *src =
"OPTION EXPLICIT\n"
"x = 42\n";
int32_t len = (int32_t)strlen(src);
BasParserT parser;
basParserInit(&parser, src, len);
bool ok = basParse(&parser);
if (!ok) {
printf("Correctly caught: %s\n", parser.error);
} else {
printf("ERROR: should have failed\n");
}
basParserFree(&parser);
printf("\n");
}
printf("All tests complete.\n");
return 0;
}