#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include <cstring>
#include "doctest.h"
#include "v4/arena.h"
#include "v4/errors.hpp"
#include "v4/internal/vm.h"
#include "v4/opcodes.hpp"
#include "v4/vm_api.h"
static void emit8(v4_u8* code, int* k, v4_u8 byte)
{
code[(*k)++] = byte;
}
TEST_CASE("VM with arena - word registration")
{
uint8_t arena_buffer[1024];
V4Arena arena;
v4_arena_init(&arena, arena_buffer, sizeof(arena_buffer));
uint8_t mem[256];
VmConfig cfg;
cfg.mem = mem;
cfg.mem_size = sizeof(mem);
cfg.mmio = nullptr;
cfg.mmio_count = 0;
cfg.arena = &arena;
Vm* vm = vm_create(&cfg);
REQUIRE(vm != nullptr);
CHECK(vm->arena == &arena);
v4_u8 code[16];
int k = 0;
emit8(code, &k, static_cast<v4_u8>(v4::Op::RET));
int idx = vm_register_word(vm, "TEST_WORD", code, k);
CHECK(idx == 0);
Word* word = vm_get_word(vm, idx);
REQUIRE(word != nullptr);
REQUIRE(word->name != nullptr);
CHECK(strcmp(word->name, "TEST_WORD") == 0);
size_t arena_used = v4_arena_used(&arena);
CHECK(arena_used > 0);
CHECK(arena_used >= strlen("TEST_WORD") + 1);
vm_destroy(vm);
}
TEST_CASE("VM with arena - multiple words")
{
uint8_t arena_buffer[2048];
V4Arena arena;
v4_arena_init(&arena, arena_buffer, sizeof(arena_buffer));
uint8_t mem[256];
VmConfig cfg;
cfg.mem = mem;
cfg.mem_size = sizeof(mem);
cfg.mmio = nullptr;
cfg.mmio_count = 0;
cfg.arena = &arena;
Vm* vm = vm_create(&cfg);
REQUIRE(vm != nullptr);
v4_u8 code[16];
int k = 0;
emit8(code, &k, static_cast<v4_u8>(v4::Op::RET));
vm_register_word(vm, "WORD1", code, k);
vm_register_word(vm, "WORD2", code, k);
vm_register_word(vm, "WORD3", code, k);
CHECK(vm->word_count == 3);
CHECK(strcmp(vm->words[0].name, "WORD1") == 0);
CHECK(strcmp(vm->words[1].name, "WORD2") == 0);
CHECK(strcmp(vm->words[2].name, "WORD3") == 0);
size_t expected_min = strlen("WORD1") + 1 + strlen("WORD2") + 1 + strlen("WORD3") + 1;
CHECK(v4_arena_used(&arena) >= expected_min);
vm_destroy(vm);
}
TEST_CASE("VM with arena - anonymous words")
{
uint8_t arena_buffer[1024];
V4Arena arena;
v4_arena_init(&arena, arena_buffer, sizeof(arena_buffer));
uint8_t mem[256];
VmConfig cfg;
cfg.mem = mem;
cfg.mem_size = sizeof(mem);
cfg.mmio = nullptr;
cfg.mmio_count = 0;
cfg.arena = &arena;
Vm* vm = vm_create(&cfg);
REQUIRE(vm != nullptr);
v4_u8 code[16];
int k = 0;
emit8(code, &k, static_cast<v4_u8>(v4::Op::RET));
size_t arena_before = v4_arena_used(&arena);
int idx = vm_register_word(vm, nullptr, code, k);
size_t arena_after = v4_arena_used(&arena);
CHECK(idx == 0);
CHECK(vm->words[0].name == nullptr);
CHECK(arena_before == arena_after);
vm_destroy(vm);
}
TEST_CASE("VM with arena - reset dictionary")
{
uint8_t arena_buffer[1024];
V4Arena arena;
v4_arena_init(&arena, arena_buffer, sizeof(arena_buffer));
uint8_t mem[256];
VmConfig cfg;
cfg.mem = mem;
cfg.mem_size = sizeof(mem);
cfg.mmio = nullptr;
cfg.mmio_count = 0;
cfg.arena = &arena;
Vm* vm = vm_create(&cfg);
REQUIRE(vm != nullptr);
v4_u8 code[16];
int k = 0;
emit8(code, &k, static_cast<v4_u8>(v4::Op::RET));
vm_register_word(vm, "WORD1", code, k);
vm_register_word(vm, "WORD2", code, k);
size_t arena_used = v4_arena_used(&arena);
CHECK(arena_used > 0);
vm_reset_dictionary(vm);
CHECK(vm->word_count == 0);
CHECK(v4_arena_used(&arena) == arena_used);
vm_destroy(vm);
}
TEST_CASE("VM with arena - arena reset and reuse")
{
uint8_t arena_buffer[1024];
V4Arena arena;
v4_arena_init(&arena, arena_buffer, sizeof(arena_buffer));
uint8_t mem[256];
VmConfig cfg;
cfg.mem = mem;
cfg.mem_size = sizeof(mem);
cfg.mmio = nullptr;
cfg.mmio_count = 0;
cfg.arena = &arena;
Vm* vm = vm_create(&cfg);
REQUIRE(vm != nullptr);
v4_u8 code[16];
int k = 0;
emit8(code, &k, static_cast<v4_u8>(v4::Op::RET));
vm_register_word(vm, "WORD1", code, k);
vm_register_word(vm, "WORD2", code, k);
size_t arena_used = v4_arena_used(&arena);
CHECK(arena_used > 0);
vm_reset_dictionary(vm);
v4_arena_reset(&arena);
CHECK(v4_arena_used(&arena) == 0);
vm_register_word(vm, "NEW_WORD1", code, k);
vm_register_word(vm, "NEW_WORD2", code, k);
CHECK(vm->word_count == 2);
CHECK(strcmp(vm->words[0].name, "NEW_WORD1") == 0);
CHECK(strcmp(vm->words[1].name, "NEW_WORD2") == 0);
vm_destroy(vm);
}
TEST_CASE("VM without arena - uses malloc")
{
uint8_t mem[256];
VmConfig cfg;
cfg.mem = mem;
cfg.mem_size = sizeof(mem);
cfg.mmio = nullptr;
cfg.mmio_count = 0;
cfg.arena = nullptr;
Vm* vm = vm_create(&cfg);
REQUIRE(vm != nullptr);
CHECK(vm->arena == nullptr);
v4_u8 code[16];
int k = 0;
emit8(code, &k, static_cast<v4_u8>(v4::Op::RET));
int idx = vm_register_word(vm, "MALLOC_WORD", code, k);
CHECK(idx == 0);
Word* word = vm_get_word(vm, idx);
REQUIRE(word != nullptr);
REQUIRE(word->name != nullptr);
CHECK(strcmp(word->name, "MALLOC_WORD") == 0);
vm_reset_dictionary(vm);
CHECK(vm->word_count == 0);
vm_destroy(vm);
}
TEST_CASE("VM with arena - out of memory")
{
uint8_t arena_buffer[32];
V4Arena arena;
v4_arena_init(&arena, arena_buffer, sizeof(arena_buffer));
uint8_t mem[256];
VmConfig cfg;
cfg.mem = mem;
cfg.mem_size = sizeof(mem);
cfg.mmio = nullptr;
cfg.mmio_count = 0;
cfg.arena = &arena;
Vm* vm = vm_create(&cfg);
REQUIRE(vm != nullptr);
v4_u8 code[16];
int k = 0;
emit8(code, &k, static_cast<v4_u8>(v4::Op::RET));
int idx1 = vm_register_word(vm, "SHORT", code, k);
CHECK(idx1 == 0);
int idx2 =
vm_register_word(vm, "THIS_IS_A_VERY_LONG_WORD_NAME_THAT_WILL_NOT_FIT", code, k);
CHECK(idx2 < 0);
vm_destroy(vm);
}