908 lines
23 KiB
C
908 lines
23 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");
|
|
}
|
|
|
|
// 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");
|
|
}
|
|
|
|
printf("All tests complete.\n");
|
|
return 0;
|
|
}
|