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 int failing_output(int64_t value, void *userdata) {
107 (void)value;
108 (void)userdata;
109 return -1;
110}
111
112static void test_vm_errors(void) {
113 VM null_vm;
114 vm_init(&null_vm, NULL);
115 assert(vm_run(&null_vm) == VM_ERR_BAD_OPERAND);
116
117 expect_program_error("POP\nHALT\n", VM_ERR_STACK_UNDERFLOW);
118 expect_program_error("PUSH 1\nPUSH 0\nDIV\nHALT\n", VM_ERR_DIVISION_BY_ZERO);
119 expect_program_error("PUSH 1\nPUSH 0\nMOD\nHALT\n", VM_ERR_DIVISION_BY_ZERO);
120 expect_program_error("RET\n", VM_ERR_CALL_UNDERFLOW);
121 expect_program_error("CALL recurse\nHALT\nrecurse:\nCALL recurse\nRET\n", VM_ERR_CALL_OVERFLOW);
122
123 Bytecode overflow;
124 bytecode_init(&overflow);
125 for (size_t i = 0; i < VM_STACK_MAX + 1; i++) {
126 assert(bytecode_emit(&overflow, OP_PUSH) == VM_OK);
127 assert(bytecode_emit(&overflow, (int64_t)i) == VM_OK);
128 }
129 assert(bytecode_emit(&overflow, OP_HALT) == VM_OK);
130 VM vm;
131 vm_init(&vm, &overflow);
132 VmError overflow_err = vm_run(&vm);
133 assert(overflow_err == VM_ERR_STACK_OVERFLOW);
134 bytecode_free(&overflow);
135
136 const int64_t bad_opcode[] = {999};
137 expect_manual_error(bad_opcode, 1, VM_ERR_BAD_OPCODE);
138
139 const int64_t bad_jump[] = {OP_JMP, 999, OP_HALT};
140 expect_manual_error(bad_jump, 3, VM_ERR_BAD_JUMP);
141
142 const int64_t bad_jump_boundary[] = {OP_JMP, 1, OP_HALT};
143 expect_manual_error(bad_jump_boundary, 3, VM_ERR_BAD_JUMP);
144
145 const int64_t bad_cond_jump[] = {OP_PUSH, 1, OP_JNZ, -1, OP_HALT};
146 expect_manual_error(bad_cond_jump, 5, VM_ERR_BAD_JUMP);
147
148 const int64_t bad_local[] = {OP_LOAD, 16, OP_HALT};
149 expect_manual_error(bad_local, 3, VM_ERR_BAD_LOCAL);
150
151 const int64_t truncated[] = {OP_PUSH};
152 expect_manual_error(truncated, 1, VM_ERR_TRUNCATED_INSTRUCTION);
153
154 const int64_t no_halt[] = {OP_PUSH, 123};
155 expect_manual_error(no_halt, 2, VM_ERR_NO_HALT);
156
157 Bytecode output_bc;
158 AsmError asm_error;
159 assert(assembler_compile_string("PUSH 1\nPRINT\nHALT\n", &output_bc, &asm_error) == 0);
160 vm_init(&vm, &output_bc);
161 vm_set_output(&vm, failing_output, NULL);
162 assert(vm_run(&vm) == VM_ERR_OUTPUT);
163 bytecode_free(&output_bc);
164}
165
166static void test_assembler_errors(void) {
167 Bytecode bc;
168 AsmError err;
169 assert(assembler_compile_string("NOPE\n", &bc, &err) != 0);
170 assert(err.line == 1);
171 assert(assembler_compile_string("x:\nx:\nHALT\n", &bc, &err) != 0);
172 assert(assembler_compile_string("JMP missing\n", &bc, &err) != 0);
173 assert(assembler_compile_string("LOAD 16\n", &bc, &err) != 0);
174 assert(assembler_compile_string("PUSH label\nlabel:\nHALT\n", &bc, &err) != 0);
175}
176
177int main(void) {
178 test_examples();
179 test_vm_errors();
180 test_assembler_errors();
181 puts("all tests passed");
182 return 0;
183}
184
Discussion
No comments yet. Start the discussion. Recorded by @patrick-toulme.