1#define _POSIX_C_SOURCE 200809L
2
3#include "asm.h"
4#include "vm.h"
5
6#include <stdio.h>
7#include <stdlib.h>
8#include <string.h>
9
10static int g_tests_run = 0;
11static int g_tests_failed = 0;
12
13static void report(int cond, const char *file, int line, const char *desc) {
14 g_tests_run++;
15 if (!cond) {
16 g_tests_failed++;
17 fprintf(stderr, "FAIL %s:%d: %s\n", file, line, desc);
18 }
19}
20#define CHECK(cond) report((cond), __FILE__, __LINE__, #cond)
21
22static char *read_file(const char *path) {
23 FILE *f = fopen(path, "rb");
24 if (f == NULL) {
25 fprintf(stderr, "cannot open %s\n", path);
26 exit(1);
27 }
28 fseek(f, 0, SEEK_END);
29 long sz = ftell(f);
30 fseek(f, 0, SEEK_SET);
31 char *buf = malloc((size_t)sz + 1);
32 size_t n = fread(buf, 1, (size_t)sz, f);
33 buf[n] = '\0';
34 fclose(f);
35 return buf;
36}
37
38typedef struct {
39 int assembled;
40 AsmStatus asm_status;
41 VMStatus vm_status;
42 char *output;
43} RunResult;
44
45static RunResult run_source(const char *source) {
46 RunResult r;
47 memset(&r, 0, sizeof(r));
48
49 uint8_t *code = NULL;
50 size_t code_size = 0;
51 AsmError aerr;
52 r.asm_status = asm_assemble(source, &code, &code_size, &aerr);
53 if (r.asm_status != ASM_OK) {
54 r.assembled = 0;
55 return r;
56 }
57 r.assembled = 1;
58
59 char *bufp = NULL;
60 size_t bufsz = 0;
61 FILE *mem = open_memstream(&bufp, &bufsz);
62
63 VM vm;
64 vm_init(&vm, code, code_size, mem);
65 r.vm_status = vm_run(&vm);
66
67 fclose(mem);
68 free(code);
69 r.output = bufp;
70 return r;
71}
72
73
74
75static void check_example(const char *path, const char *expected) {
76 char *source = read_file(path);
77 RunResult r = run_source(source);
78 CHECK(r.assembled);
79 if (r.assembled) {
80 CHECK(r.vm_status == VM_OK);
81 int match = r.output != NULL && strcmp(r.output, expected) == 0;
82 CHECK(match);
83 if (!match) {
84 fprintf(stderr, " in %s\n expected: %s\n actual: %s\n", path, expected,
85 r.output ? r.output : "(null)");
86 }
87 }
88 free(r.output);
89 free(source);
90}
91
92static void test_fib(void) { check_example("examples/fib.vasm", "6765\n"); }
93static void test_fact(void) { check_example("examples/fact.vasm", "3628800\n"); }
94static void test_primes(void) {
95 check_example("examples/primes.vasm",
96 "2\n3\n5\n7\n11\n13\n17\n19\n23\n29\n31\n37\n41\n43\n47\n");
97}
98
99
100
101static void test_arithmetic_ops(void) {
102 RunResult r = run_source(
103 "PUSH 3\nPUSH 4\nADD\nPRINT\n"
104 "PUSH 10\nPUSH 3\nSUB\nPRINT\n"
105 "PUSH 6\nPUSH 7\nMUL\nPRINT\n"
106 "PUSH 20\nPUSH 6\nDIV\nPRINT\n"
107 "PUSH 20\nPUSH 6\nMOD\nPRINT\n"
108 "PUSH 5\nNEG\nPRINT\n"
109 "PUSH 3\nPUSH 3\nEQ\nPRINT\n"
110 "PUSH 3\nPUSH 4\nLT\nPRINT\n"
111 "PUSH 4\nPUSH 3\nGT\nPRINT\n"
112 "PUSH 1\nDUP\nADD\nPRINT\n"
113 "PUSH 1\nPUSH 2\nSWAP\nSUB\nPRINT\n"
114 "HALT\n");
115 CHECK(r.assembled);
116 CHECK(r.vm_status == VM_OK);
117 const char *expected = "7\n7\n42\n3\n2\n-5\n1\n1\n1\n2\n1\n";
118 CHECK(r.output != NULL && strcmp(r.output, expected) == 0);
119 free(r.output);
120}
121
122static void test_call_ret(void) {
123 RunResult r = run_source(
124 "PUSH 41\n"
125 "CALL inc\n"
126 "PRINT\n"
127 "HALT\n"
128 "inc:\n"
129 " STORE 0\n"
130 " LOAD 0\n"
131 " PUSH 1\n"
132 " ADD\n"
133 " RET\n");
134 CHECK(r.assembled);
135 CHECK(r.vm_status == VM_OK);
136 CHECK(r.output != NULL && strcmp(r.output, "42\n") == 0);
137 free(r.output);
138}
139
140
141
142static void test_stack_underflow(void) {
143 RunResult r = run_source("ADD\nHALT\n");
144 CHECK(r.assembled);
145 CHECK(r.vm_status == VM_ERR_STACK_UNDERFLOW);
146 free(r.output);
147}
148
149static void test_stack_overflow(void) {
150 RunResult r = run_source("loop:\n PUSH 1\n JMP loop\n");
151 CHECK(r.assembled);
152 CHECK(r.vm_status == VM_ERR_STACK_OVERFLOW);
153 free(r.output);
154}
155
156static void test_div_zero(void) {
157 RunResult r = run_source("PUSH 5\nPUSH 0\nDIV\nHALT\n");
158 CHECK(r.assembled);
159 CHECK(r.vm_status == VM_ERR_DIV_ZERO);
160 free(r.output);
161}
162
163static void test_mod_zero(void) {
164 RunResult r = run_source("PUSH 5\nPUSH 0\nMOD\nHALT\n");
165 CHECK(r.assembled);
166 CHECK(r.vm_status == VM_ERR_DIV_ZERO);
167 free(r.output);
168}
169
170static void test_call_stack_overflow(void) {
171 RunResult r = run_source("loop:\n CALL loop\n");
172 CHECK(r.assembled);
173 CHECK(r.vm_status == VM_ERR_CALLSTACK_OVERFLOW);
174 free(r.output);
175}
176
177static void test_call_stack_underflow(void) {
178 RunResult r = run_source("RET\nHALT\n");
179 CHECK(r.assembled);
180 CHECK(r.vm_status == VM_ERR_CALLSTACK_UNDERFLOW);
181 free(r.output);
182}
183
184
185
186
187static void test_vm_bad_opcode(void) {
188 uint8_t code[] = {0xFF};
189 VM vm;
190 vm_init(&vm, code, sizeof(code), NULL);
191 VMStatus st = vm_run(&vm);
192 CHECK(st == VM_ERR_BAD_OPCODE);
193}
194
195static void test_vm_out_of_range_jump(void) {
196 uint8_t code[5];
197 code[0] = (uint8_t)OP_JMP;
198 int32_t target = 9999;
199 memcpy(&code[1], &target, sizeof(target));
200 VM vm;
201 vm_init(&vm, code, sizeof(code), NULL);
202 VMStatus st = vm_run(&vm);
203 CHECK(st == VM_ERR_BAD_JUMP);
204}
205
206static void test_vm_negative_jump_target(void) {
207 uint8_t code[5];
208 code[0] = (uint8_t)OP_JMP;
209 int32_t target = -1;
210 memcpy(&code[1], &target, sizeof(target));
211 VM vm;
212 vm_init(&vm, code, sizeof(code), NULL);
213 VMStatus st = vm_run(&vm);
214 CHECK(st == VM_ERR_BAD_JUMP);
215}
216
217static void test_vm_bad_local_index(void) {
218 uint8_t code[2];
219 code[0] = (uint8_t)OP_LOAD;
220 code[1] = 200;
221 VM vm;
222 vm_init(&vm, code, sizeof(code), NULL);
223 VMStatus st = vm_run(&vm);
224 CHECK(st == VM_ERR_BAD_LOCAL_INDEX);
225}
226
227static void test_vm_truncated_instruction(void) {
228 uint8_t code[1] = {(uint8_t)OP_PUSH};
229 VM vm;
230 vm_init(&vm, code, sizeof(code), NULL);
231 VMStatus st = vm_run(&vm);
232 CHECK(st == VM_ERR_TRUNCATED);
233}
234
235static void test_vm_no_code(void) {
236 VM vm;
237 vm_init(&vm, NULL, 0, NULL);
238 VMStatus st = vm_run(&vm);
239 CHECK(st == VM_ERR_NO_CODE);
240}
241
242
243
244static void test_asm_unknown_mnemonic(void) {
245 AsmError err;
246 uint8_t *code;
247 size_t size;
248 AsmStatus st = asm_assemble("FOO\nHALT\n", &code, &size, &err);
249 CHECK(st == ASM_ERR_UNKNOWN_MNEMONIC);
250 CHECK(code == NULL && size == 0);
251}
252
253static void test_asm_unknown_label(void) {
254 AsmError err;
255 uint8_t *code;
256 size_t size;
257 AsmStatus st = asm_assemble("JMP nowhere\nHALT\n", &code, &size, &err);
258 CHECK(st == ASM_ERR_UNKNOWN_LABEL);
259 CHECK(code == NULL && size == 0);
260}
261
262static void test_asm_duplicate_label(void) {
263 AsmError err;
264 uint8_t *code;
265 size_t size;
266 AsmStatus st = asm_assemble("a: HALT\na: HALT\n", &code, &size, &err);
267 CHECK(st == ASM_ERR_DUPLICATE_LABEL);
268 CHECK(code == NULL && size == 0);
269}
270
271static void test_asm_missing_operand(void) {
272 AsmError err;
273 uint8_t *code;
274 size_t size;
275 AsmStatus st = asm_assemble("PUSH\nHALT\n", &code, &size, &err);
276 CHECK(st == ASM_ERR_MISSING_OPERAND);
277 CHECK(code == NULL && size == 0);
278}
279
280static void test_asm_unexpected_operand(void) {
281 AsmError err;
282 uint8_t *code;
283 size_t size;
284 AsmStatus st = asm_assemble("HALT 1\n", &code, &size, &err);
285 CHECK(st == ASM_ERR_UNEXPECTED_OPERAND);
286 CHECK(code == NULL && size == 0);
287}
288
289static void test_asm_bad_local_index(void) {
290 AsmError err;
291 uint8_t *code;
292 size_t size;
293 AsmStatus st = asm_assemble("LOAD 16\nHALT\n", &code, &size, &err);
294 CHECK(st == ASM_ERR_BAD_OPERAND);
295 CHECK(code == NULL && size == 0);
296}
297
298static void test_asm_bad_integer_operand(void) {
299 AsmError err;
300 uint8_t *code;
301 size_t size;
302 AsmStatus st = asm_assemble("PUSH abc\nHALT\n", &code, &size, &err);
303 CHECK(st == ASM_ERR_BAD_OPERAND);
304 CHECK(code == NULL && size == 0);
305}
306
307static void test_asm_invalid_label_name(void) {
308 AsmError err;
309 uint8_t *code;
310 size_t size;
311 AsmStatus st = asm_assemble("9bad: HALT\n", &code, &size, &err);
312 CHECK(st == ASM_ERR_BAD_LABEL_NAME);
313 CHECK(code == NULL && size == 0);
314}
315
316static void test_asm_comments_and_whitespace(void) {
317 RunResult r = run_source(
318 "; leading comment\n"
319 " PUSH 1 ; push one\n"
320 "\n"
321 "# also a comment style\n"
322 " PUSH 2\n"
323 " ADD\n"
324 " PRINT\n"
325 " HALT\n");
326 CHECK(r.assembled);
327 CHECK(r.vm_status == VM_OK);
328 CHECK(r.output != NULL && strcmp(r.output, "3\n") == 0);
329 free(r.output);
330}
331
332int main(void) {
333 test_fib();
334 test_fact();
335 test_primes();
336
337 test_arithmetic_ops();
338 test_call_ret();
339 test_asm_comments_and_whitespace();
340
341 test_stack_underflow();
342 test_stack_overflow();
343 test_div_zero();
344 test_mod_zero();
345 test_call_stack_overflow();
346 test_call_stack_underflow();
347
348 test_vm_bad_opcode();
349 test_vm_out_of_range_jump();
350 test_vm_negative_jump_target();
351 test_vm_bad_local_index();
352 test_vm_truncated_instruction();
353 test_vm_no_code();
354
355 test_asm_unknown_mnemonic();
356 test_asm_unknown_label();
357 test_asm_duplicate_label();
358 test_asm_missing_operand();
359 test_asm_unexpected_operand();
360 test_asm_bad_local_index();
361 test_asm_bad_integer_operand();
362 test_asm_invalid_label_name();
363
364 printf("%d/%d tests passed\n", g_tests_run - g_tests_failed, g_tests_run);
365 return g_tests_failed == 0 ? 0 : 1;
366}
367
Discussion
No comments yet. Start the discussion. Recorded by @patrick-toulme.