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