DVX_GUI/apps/dvxbasic/test_compact.c

326 lines
7.4 KiB
C

// test_compact.c -- verify that strip + compact produces identical output
//
// Compiles each test program, runs it once without compaction and once
// with strip + compact applied, and compares captured PRINT output.
// Exits nonzero if any mismatch is found.
#include "compiler/parser.h"
#include "compiler/strip.h"
#include "compiler/compact.h"
#include "runtime/vm.h"
#include "runtime/values.h"
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_OUT 65536
typedef struct {
char *buf;
int32_t len;
int32_t cap;
} OutBufT;
static void captureCallback(void *ctx, const char *text, bool newline) {
OutBufT *ob = (OutBufT *)ctx;
int32_t tlen = (int32_t)strlen(text);
if (ob->len + tlen + 2 >= ob->cap) {
return;
}
memcpy(ob->buf + ob->len, text, tlen);
ob->len += tlen;
if (newline) {
ob->buf[ob->len++] = '\n';
}
}
static int32_t runAndCapture(const char *source, bool compact, char *outBuf, int32_t outCap) {
BasParserT parser;
basParserInit(&parser, source, (int32_t)strlen(source));
if (!basParse(&parser)) {
fprintf(stderr, "compile error: %s\n", parser.error);
basParserFree(&parser);
return -1;
}
BasModuleT *mod = basParserBuildModule(&parser);
basParserFree(&parser);
if (!mod) {
return -1;
}
if (compact) {
basStripModule(mod);
basCompactBytecode(mod);
}
BasVmT *vm = basVmCreate();
basVmLoadModule(vm, mod);
OutBufT ob;
ob.buf = outBuf;
ob.len = 0;
ob.cap = outCap;
basVmSetPrintCallback(vm, captureCallback, &ob);
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) {
fprintf(stderr, "vm error %d: %s\n", result, basVmGetError(vm));
}
basVmDestroy(vm);
basModuleFree(mod);
outBuf[ob.len] = '\0';
return ob.len;
}
static int32_t sTotal = 0;
static int32_t sFailed = 0;
static void testCompact(const char *name, const char *source) {
sTotal++;
static char orig[MAX_OUT];
static char comp[MAX_OUT];
int32_t origLen = runAndCapture(source, false, orig, MAX_OUT);
int32_t compLen = runAndCapture(source, true, comp, MAX_OUT);
if (origLen < 0 || compLen < 0) {
printf("FAIL: %s (runtime error)\n", name);
sFailed++;
return;
}
if (origLen != compLen || memcmp(orig, comp, origLen) != 0) {
printf("FAIL: %s\n", name);
printf(" uncompacted (%d bytes):\n%s\n", (int)origLen, orig);
printf(" compacted (%d bytes):\n%s\n", (int)compLen, comp);
sFailed++;
return;
}
printf("PASS: %s\n", name);
}
int main(void) {
printf("DVX BASIC Bytecode Compaction Tests\n");
printf("====================================\n\n");
basStringSystemInit();
// ---- Basic control flow ----
testCompact("FOR loop",
"DIM i AS INTEGER\n"
"FOR i = 1 TO 5\n"
" PRINT i;\n"
"NEXT i\n"
"PRINT\n"
);
testCompact("Nested FOR",
"DIM i AS INTEGER\n"
"DIM j AS INTEGER\n"
"FOR i = 1 TO 3\n"
" FOR j = 1 TO 2\n"
" PRINT i * 10 + j;\n"
" NEXT j\n"
"NEXT i\n"
"PRINT\n"
);
testCompact("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"
);
testCompact("EXIT FOR",
"DIM i AS INTEGER\n"
"FOR i = 1 TO 100\n"
" IF i = 4 THEN EXIT FOR\n"
" PRINT i;\n"
"NEXT i\n"
"PRINT\n"
);
testCompact("DO WHILE",
"DIM n AS INTEGER\n"
"n = 0\n"
"DO WHILE n < 3\n"
" PRINT n;\n"
" n = n + 1\n"
"LOOP\n"
"PRINT\n"
);
testCompact("IF THEN ELSE",
"DIM x AS INTEGER\n"
"x = 5\n"
"IF x > 3 THEN\n"
" PRINT \"big\"\n"
"ELSE\n"
" PRINT \"small\"\n"
"END IF\n"
);
testCompact("SELECT CASE",
"DIM g AS STRING\n"
"g = \"B\"\n"
"SELECT CASE g\n"
" CASE \"A\"\n"
" PRINT \"A\"\n"
" CASE \"B\", \"C\"\n"
" PRINT \"BC\"\n"
" CASE ELSE\n"
" PRINT \"?\"\n"
"END SELECT\n"
);
// ---- GOSUB: exercises the absolute address in OP_PUSH_INT32 pattern ----
testCompact("GOSUB",
"DIM n AS INTEGER\n"
"n = 10\n"
"GOSUB doubler\n"
"PRINT n\n"
"GOSUB doubler\n"
"PRINT n\n"
"END\n"
"doubler:\n"
"n = n * 2\n"
"RETURN\n"
);
testCompact("Multiple GOSUBs",
"DIM x AS INTEGER\n"
"x = 1\n"
"GOSUB a\n"
"GOSUB b\n"
"GOSUB c\n"
"PRINT x\n"
"END\n"
"a:\n"
"x = x + 10\n"
"RETURN\n"
"b:\n"
"x = x + 100\n"
"RETURN\n"
"c:\n"
"x = x + 1000\n"
"RETURN\n"
);
// ---- SUB / FUNCTION: exercises absolute CALL addresses + proc table ----
testCompact("SUB with CALL",
"CALL Greet\n"
"CALL Greet\n"
"CALL Greet\n"
"SUB Greet\n"
" PRINT \"Hello\"\n"
"END SUB\n"
);
testCompact("FUNCTION return value",
"DECLARE FUNCTION Square(x AS INTEGER) AS INTEGER\n"
"DIM r AS INTEGER\n"
"r = Square(5)\n"
"PRINT r\n"
"r = Square(7)\n"
"PRINT r\n"
"FUNCTION Square(x AS INTEGER) AS INTEGER\n"
" Square = x * x\n"
"END FUNCTION\n"
);
testCompact("Recursive FUNCTION",
"DECLARE FUNCTION Fact(n AS INTEGER) AS INTEGER\n"
"PRINT Fact(5)\n"
"FUNCTION Fact(n AS INTEGER) AS INTEGER\n"
" IF n <= 1 THEN\n"
" Fact = 1\n"
" ELSE\n"
" Fact = n * Fact(n - 1)\n"
" END IF\n"
"END FUNCTION\n"
);
// ---- ON ERROR: exercises the relative int16 offset path with non-zero ----
testCompact("ON ERROR GOTO",
"ON ERROR GOTO handler\n"
"PRINT 10 / 0\n"
"PRINT \"unreached\"\n"
"END\n"
"handler:\n"
"PRINT \"caught\"\n"
);
// ---- Mixed: many OP_LINE between jumps ----
testCompact("Long function with many lines",
"DIM total AS INTEGER\n"
"total = 0\n"
"DIM i AS INTEGER\n"
"FOR i = 1 TO 10\n"
" IF i MOD 2 = 0 THEN\n"
" total = total + i\n"
" ELSE\n"
" total = total + i * 2\n"
" END IF\n"
"NEXT i\n"
"PRINT total\n"
);
testCompact("GOSUB inside FOR",
"DIM i AS INTEGER\n"
"DIM sum AS INTEGER\n"
"sum = 0\n"
"FOR i = 1 TO 5\n"
" GOSUB addit\n"
"NEXT i\n"
"PRINT sum\n"
"END\n"
"addit:\n"
"sum = sum + i\n"
"RETURN\n"
);
testCompact("FOR inside SUB",
"CALL Loop5\n"
"SUB Loop5\n"
" DIM k AS INTEGER\n"
" FOR k = 0 TO 4\n"
" PRINT k;\n"
" NEXT k\n"
" PRINT\n"
"END SUB\n"
);
printf("\n%d/%d tests passed\n", (int)(sTotal - sFailed), (int)sTotal);
return sFailed > 0 ? 1 : 0;
}