1#include "vm.h"
2
3#include <assert.h>
4#include <stdio.h>
5#include <stdlib.h>
6#include <string.h>
7
8typedef struct OutputBuffer {
9 char data[8192];
10 size_t len;
11} OutputBuffer;
12
13static int capture_output(int64_t value, void *userdata) {
14 OutputBuffer *out = (OutputBuffer *)userdata;
15 int written = snprintf(out->data + out->len, sizeof(out->data) - out->len,
16 "%lld\n", (long long)value);
17 if (written < 0 || (size_t)written >= sizeof(out->data) - out->len) {
18 return -1;
19 }
20 out->len += (size_t)written;
21 return 0;
22}
23
24static void assert_vm_ok(VmError err, const char *context) {
25 if (err != VM_OK) {
26 fprintf(stderr, "%s: %s\n", context, vm_error_string(err));
27 assert(err == VM_OK);
28 }
29}
30
31static void assert_asm_ok(int rc, const AsmError *err, const char *path) {
32 if (rc != 0) {
33 fprintf(stderr, "%s:%d: assembler error: %s\n", path, err->line, err->message);
34 assert(rc == 0);
35 }
36}
37
38static void run_example(const char *path, const char *expected) {
39 Bytecode bc;
40 AsmError asm_error;
41 int rc = assembler_compile_file(path, &bc, &asm_error);
42 assert_asm_ok(rc, &asm_error, path);
43
44 VM vm;
45 OutputBuffer out = {{0}, 0};
46 vm_init(&vm, &bc);
47 vm_set_output(&vm, capture_output, &out);
48 assert_vm_ok(vm_run(&vm), path);
49
50 if (strcmp(out.data, expected) != 0) {
51 fprintf(stderr, "%s: output mismatch\nexpected:\n%sactual:\n%s", path, expected, out.data);
52 assert(strcmp(out.data, expected) == 0);
53 }
54 bytecode_free(&bc);
55}
56
57static Bytecode make_bytecode(const int64_t *words, size_t count) {
58 Bytecode bc;
59 bytecode_init(&bc);
60 for (size_t i = 0; i < count; i++) {
61 assert(bytecode_emit(&bc, words[i]) == VM_OK);
62 }
63 return bc;
64}
65
66static void expect_program_error(const char *source, VmError expected) {
67 Bytecode bc;
68 AsmError asm_error;
69 int rc = assembler_compile_string(source, &bc, &asm_error);
70 assert_asm_ok(rc, &asm_error, "inline");
71
72 VM vm;
73 OutputBuffer out = {{0}, 0};
74 vm_init(&vm, &bc);
75 vm_set_output(&vm, capture_output, &out);
76 VmError actual = vm_run(&vm);
77 if (actual != expected) {
78 fprintf(stderr, "expected %s, got %s for program:\n%s\n",
79 vm_error_string(expected), vm_error_string(actual), source);
80 assert(actual == expected);
81 }
82 bytecode_free(&bc);
83}
84
85static void expect_manual_error(const int64_t *words, size_t count, VmError expected) {
86 Bytecode bc = make_bytecode(words, count);
87 VM vm;
88 OutputBuffer out = {{0}, 0};
89 vm_init(&vm, &bc);
90 vm_set_output(&vm, capture_output, &out);
91 VmError actual = vm_run(&vm);
92 if (actual != expected) {
93 fprintf(stderr, "expected %s, got %s\n", vm_error_string(expected), vm_error_string(actual));
94 assert(actual == expected);
95 }
96 bytecode_free(&bc);
97}
98
99static void test_examples(void) {
100 run_example("examples/fib.vasm", "6765\n");
101 run_example("examples/fact.vasm", "3628800\n");
102 run_example("examples/primes.vasm",
103 "2\n3\n5\n7\n11\n13\n17\n19\n23\n29\n31\n37\n41\n43\n47\n");
104}
105
106static void test_vm_errors(void) {
107 expect_program_error("POP\nHALT\n", VM_ERR_STACK_UNDERFLOW);
108 expect_program_error("PUSH 1\nPUSH 0\nDIV\nHALT\n", VM_ERR_DIVISION_BY_ZERO);
109 expect_program_error("PUSH 1\nPUSH 0\nMOD\nHALT\n", VM_ERR_DIVISION_BY_ZERO);
110 expect_program_error("RET\n", VM_ERR_CALL_UNDERFLOW);
111 expect_program_error("CALL recurse\nHALT\nrecurse:\nCALL recurse\nRET\n", VM_ERR_CALL_OVERFLOW);
112
113 Bytecode overflow;
114 bytecode_init(&overflow);
115 for (size_t i = 0; i < VM_STACK_MAX + 1; i++) {
116 assert(bytecode_emit(&overflow, OP_PUSH) == VM_OK);
117 assert(bytecode_emit(&overflow, (int64_t)i) == VM_OK);
118 }
119 assert(bytecode_emit(&overflow, OP_HALT) == VM_OK);
120 VM vm;
121 vm_init(&vm, &overflow);
122 VmError overflow_err = vm_run(&vm);
123 assert(overflow_err == VM_ERR_STACK_OVERFLOW);
124 bytecode_free(&overflow);
125
126 const int64_t bad_opcode[] = {999};
127 expect_manual_error(bad_opcode, 1, VM_ERR_BAD_OPCODE);
128
129 const int64_t bad_jump[] = {OP_JMP, 999, OP_HALT};
130 expect_manual_error(bad_jump, 3, VM_ERR_BAD_JUMP);
131
132 const int64_t bad_cond_jump[] = {OP_PUSH, 1, OP_JNZ, -1, OP_HALT};
133 expect_manual_error(bad_cond_jump, 5, VM_ERR_BAD_JUMP);
134
135 const int64_t bad_local[] = {OP_LOAD, 16, OP_HALT};
136 expect_manual_error(bad_local, 3, VM_ERR_BAD_LOCAL);
137
138 const int64_t truncated[] = {OP_PUSH};
139 expect_manual_error(truncated, 1, VM_ERR_TRUNCATED_INSTRUCTION);
140
141 const int64_t no_halt[] = {OP_PUSH, 123};
142 expect_manual_error(no_halt, 2, VM_ERR_NO_HALT);
143}
144
145static void test_assembler_errors(void) {
146 Bytecode bc;
147 AsmError err;
148 assert(assembler_compile_string("NOPE\n", &bc, &err) != 0);
149 assert(err.line == 1);
150 assert(assembler_compile_string("x:\nx:\nHALT\n", &bc, &err) != 0);
151 assert(assembler_compile_string("JMP missing\n", &bc, &err) != 0);
152 assert(assembler_compile_string("LOAD 16\n", &bc, &err) != 0);
153 assert(assembler_compile_string("PUSH label\nlabel:\nHALT\n", &bc, &err) != 0);
154}
155
156int main(void) {
157 test_examples();
158 test_vm_errors();
159 test_assembler_errors();
160 puts("all tests passed");
161 return 0;
162}
163
Discussion
No comments yet. Start the discussion. Recorded by @patrick-toulme.