joeyagi/tests/testAgiVm.c

135 lines
3.7 KiB
C

// Host-side test for examples/agi/agiVm.c (Phase 1 sub-A).
//
// Loads a LOGIC resource (default 0), runs the VM until it halts,
// and prints the halt reason plus any non-zero vars/flags. Since
// only the arithmetic and flag opcodes (0x00-0x11) are implemented
// this pass, expectation is one of:
// - HALT_RETURN at offset N (logic was entirely sub-A opcodes)
// - HALT_UNKNOWN_OP 0xXX at offset N (hit IF / new.room / etc.)
// - HALT_TRUNCATED at offset N (malformed resource)
//
// Usage:
// testAgiVm <game-directory> [logic-id]
#include "agi.h"
#include <stdio.h>
#include <stdlib.h>
// ----- Prototypes -----
static const char *haltName(AgiVmHaltE r);
static void printNonZeroState(const AgiVmT *vm);
// ----- Helpers (alphabetical) -----
static const char *haltName(AgiVmHaltE r) {
switch (r) {
case AGI_VM_HALT_NONE: return "NONE";
case AGI_VM_HALT_RETURN: return "RETURN";
case AGI_VM_HALT_UNKNOWN_OP: return "UNKNOWN_OP";
case AGI_VM_HALT_TRUNCATED: return "TRUNCATED";
case AGI_VM_HALT_NEW_ROOM: return "NEW_ROOM";
default: return "?";
}
}
static void printNonZeroState(const AgiVmT *vm) {
uint16_t i;
uint16_t nonZeroVars;
uint16_t nonZeroFlags;
nonZeroVars = 0u;
nonZeroFlags = 0u;
for (i = 0u; i < AGI_VM_NUM_VARS; i++) {
if (vm->vars[i] != 0u) {
if (nonZeroVars == 0u) {
printf(" vars:");
}
printf(" v%u=%u", (unsigned)i, (unsigned)vm->vars[i]);
nonZeroVars++;
}
}
if (nonZeroVars > 0u) {
printf("\n");
}
for (i = 0u; i < AGI_VM_NUM_FLAGS; i++) {
if (vm->flags[i] != 0u) {
if (nonZeroFlags == 0u) {
printf(" flags:");
}
printf(" f%u", (unsigned)i);
nonZeroFlags++;
}
}
if (nonZeroFlags > 0u) {
printf("\n");
}
}
// ----- main -----
int main(int argc, char **argv) {
AgiGameT game;
AgiVmT vm;
uint16_t logicId;
uint8_t *bytes;
uint16_t length;
AgiVmHaltE reason;
if (argc < 2 || argc > 3) {
fprintf(stderr, "usage: %s <game-directory> [logic-id]\n", argv[0]);
return 2;
}
if (!agiResOpen(&game, argv[1])) {
fprintf(stderr, "FAIL: agiResOpen rejected '%s'\n", argv[1]);
return 1;
}
logicId = (argc == 3) ? (uint16_t)atoi(argv[2]) : 0u;
if (logicId >= game.resCount[AGI_RES_LOGIC]) {
fprintf(stderr, "FAIL: no LOGIC at id %u\n", (unsigned)logicId);
agiResClose(&game);
return 1;
}
bytes = agiResLoad(&game, AGI_RES_LOGIC, logicId, &length);
if (bytes == NULL) {
fprintf(stderr, "FAIL: LOGIC %u failed to load\n", (unsigned)logicId);
agiResClose(&game);
return 1;
}
agiVmInit(&vm);
if (!agiVmLoadLogic(&vm, bytes, length)) {
fprintf(stderr, "FAIL: agiVmLoadLogic rejected LOGIC %u (truncated header?)\n", (unsigned)logicId);
free(bytes);
agiResClose(&game);
return 1;
}
printf("LOGIC %u: %u bytes resource, %u bytes bytecode\n",
(unsigned)logicId, (unsigned)length, (unsigned)vm.codeLength);
reason = agiVmRun(&vm);
printf(" halt=%s at pc=%u/%u",
haltName(reason), (unsigned)vm.pc, (unsigned)vm.codeLength);
if (reason == AGI_VM_HALT_UNKNOWN_OP) {
printf(" opcode=0x%02X", (unsigned)vm.lastUnknownOp);
}
if (reason == AGI_VM_HALT_NEW_ROOM) {
printf(" newRoom=%u", (unsigned)vm.newRoomId);
}
printf("\n");
printNonZeroState(&vm);
free(bytes);
agiResClose(&game);
return 0;
}