1#include "vm.h"
2
3#include <ctype.h>
4#include <errno.h>
5#include <stdio.h>
6#include <stdlib.h>
7#include <string.h>
8
9typedef struct Symbol {
10 char *name;
11 size_t address;
12} Symbol;
13
14typedef struct SymbolTable {
15 Symbol *items;
16 size_t count;
17 size_t capacity;
18} SymbolTable;
19
20static char *xstrdup(const char *s) {
21 size_t n = strlen(s) + 1;
22 char *copy = (char *)malloc(n);
23 if (copy) {
24 memcpy(copy, s, n);
25 }
26 return copy;
27}
28
29static void set_error(AsmError *error, int line, const char *message) {
30 if (!error) {
31 return;
32 }
33 error->line = line;
34 snprintf(error->message, sizeof(error->message), "%s", message);
35}
36
37static void symbol_table_free(SymbolTable *table) {
38 for (size_t i = 0; i < table->count; i++) {
39 free(table->items[i].name);
40 }
41 free(table->items);
42 table->items = NULL;
43 table->count = 0;
44 table->capacity = 0;
45}
46
47static Symbol *symbol_find(SymbolTable *table, const char *name) {
48 for (size_t i = 0; i < table->count; i++) {
49 if (strcmp(table->items[i].name, name) == 0) {
50 return &table->items[i];
51 }
52 }
53 return NULL;
54}
55
56static int symbol_add(SymbolTable *table, const char *name, size_t address, AsmError *error, int line) {
57 if (symbol_find(table, name)) {
58 set_error(error, line, "duplicate label");
59 return -1;
60 }
61 if (table->count == table->capacity) {
62 size_t new_capacity = table->capacity == 0 ? 32 : table->capacity * 2;
63 Symbol *new_items = (Symbol *)realloc(table->items, new_capacity * sizeof(*new_items));
64 if (!new_items) {
65 set_error(error, line, "out of memory");
66 return -1;
67 }
68 table->items = new_items;
69 table->capacity = new_capacity;
70 }
71 table->items[table->count].name = xstrdup(name);
72 if (!table->items[table->count].name) {
73 set_error(error, line, "out of memory");
74 return -1;
75 }
76 table->items[table->count].address = address;
77 table->count++;
78 return 0;
79}
80
81static char *trim(char *s) {
82 while (isspace((unsigned char)*s)) {
83 s++;
84 }
85 if (*s == '\0') {
86 return s;
87 }
88 char *end = s + strlen(s) - 1;
89 while (end > s && isspace((unsigned char)*end)) {
90 *end-- = '\0';
91 }
92 return s;
93}
94
95static int valid_label_name(const char *s) {
96 if (!(isalpha((unsigned char)s[0]) || s[0] == '_')) {
97 return 0;
98 }
99 for (size_t i = 1; s[i] != '\0'; i++) {
100 if (!(isalnum((unsigned char)s[i]) || s[i] == '_')) {
101 return 0;
102 }
103 }
104 return 1;
105}
106
107static void upper_ascii(char *s) {
108 for (; *s; s++) {
109 *s = (char)toupper((unsigned char)*s);
110 }
111}
112
113static int instruction_info(const char *mnemonic, VmOpcode *opcode, int *operands) {
114 char upper[32];
115 if (strlen(mnemonic) >= sizeof(upper)) {
116 return -1;
117 }
118 strcpy(upper, mnemonic);
119 upper_ascii(upper);
120
121 *operands = 0;
122 if (strcmp(upper, "PUSH") == 0) { *opcode = OP_PUSH; *operands = 1; return 0; }
123 if (strcmp(upper, "POP") == 0) { *opcode = OP_POP; return 0; }
124 if (strcmp(upper, "DUP") == 0) { *opcode = OP_DUP; return 0; }
125 if (strcmp(upper, "SWAP") == 0) { *opcode = OP_SWAP; return 0; }
126 if (strcmp(upper, "ADD") == 0) { *opcode = OP_ADD; return 0; }
127 if (strcmp(upper, "SUB") == 0) { *opcode = OP_SUB; return 0; }
128 if (strcmp(upper, "MUL") == 0) { *opcode = OP_MUL; return 0; }
129 if (strcmp(upper, "DIV") == 0) { *opcode = OP_DIV; return 0; }
130 if (strcmp(upper, "MOD") == 0) { *opcode = OP_MOD; return 0; }
131 if (strcmp(upper, "NEG") == 0) { *opcode = OP_NEG; return 0; }
132 if (strcmp(upper, "EQ") == 0) { *opcode = OP_EQ; return 0; }
133 if (strcmp(upper, "LT") == 0) { *opcode = OP_LT; return 0; }
134 if (strcmp(upper, "GT") == 0) { *opcode = OP_GT; return 0; }
135 if (strcmp(upper, "JMP") == 0) { *opcode = OP_JMP; *operands = 1; return 0; }
136 if (strcmp(upper, "JZ") == 0) { *opcode = OP_JZ; *operands = 1; return 0; }
137 if (strcmp(upper, "JNZ") == 0) { *opcode = OP_JNZ; *operands = 1; return 0; }
138 if (strcmp(upper, "CALL") == 0) { *opcode = OP_CALL; *operands = 1; return 0; }
139 if (strcmp(upper, "RET") == 0) { *opcode = OP_RET; return 0; }
140 if (strcmp(upper, "LOAD") == 0) { *opcode = OP_LOAD; *operands = 1; return 0; }
141 if (strcmp(upper, "STORE") == 0) { *opcode = OP_STORE; *operands = 1; return 0; }
142 if (strcmp(upper, "PRINT") == 0) { *opcode = OP_PRINT; return 0; }
143 if (strcmp(upper, "HALT") == 0) { *opcode = OP_HALT; return 0; }
144 return -1;
145}
146
147static int parse_int64(const char *s, int64_t *out) {
148 char *end = NULL;
149 errno = 0;
150 long long v = strtoll(s, &end, 0);
151 if (errno != 0 || end == s || *end != '\0') {
152 return -1;
153 }
154 *out = (int64_t)v;
155 return 0;
156}
157
158static int parse_operand(char *operand_text, VmOpcode opcode, SymbolTable *symbols, int pass,
159 int64_t *operand, AsmError *error, int line) {
160 char *tok = strtok(operand_text, " \t\r\n");
161 if (!tok) {
162 set_error(error, line, "missing operand");
163 return -1;
164 }
165 if (strtok(NULL, " \t\r\n") != NULL) {
166 set_error(error, line, "too many operands");
167 return -1;
168 }
169
170 int needs_label_resolution = (opcode == OP_JMP || opcode == OP_JZ || opcode == OP_JNZ || opcode == OP_CALL);
171 if (parse_int64(tok, operand) == 0) {
172 if ((opcode == OP_LOAD || opcode == OP_STORE) && (*operand < 0 || *operand >= VM_LOCALS_PER_FRAME)) {
173 set_error(error, line, "local index out of range");
174 return -1;
175 }
176 return 0;
177 }
178
179 if (!needs_label_resolution) {
180 set_error(error, line, "operand must be an integer");
181 return -1;
182 }
183
184 if (!valid_label_name(tok)) {
185 set_error(error, line, "invalid label reference");
186 return -1;
187 }
188
189 if (pass == 1) {
190 *operand = 0;
191 return 0;
192 }
193
194 Symbol *symbol = symbol_find(symbols, tok);
195 if (!symbol) {
196 set_error(error, line, "undefined label");
197 return -1;
198 }
199 *operand = (int64_t)symbol->address;
200 return 0;
201}
202
203static int process_line(char *line, int line_no, int pass, SymbolTable *symbols,
204 Bytecode *out, size_t *pc, AsmError *error) {
205 for (char *p = line; *p; p++) {
206 if (*p == ';' || *p == '#') {
207 *p = '\0';
208 break;
209 }
210 if (*p == ',') {
211 *p = ' ';
212 }
213 }
214
215 char *s = trim(line);
216 while (*s) {
217 char *colon = strchr(s, ':');
218 if (!colon) {
219 break;
220 }
221 *colon = '\0';
222 char *label = trim(s);
223 if (!valid_label_name(label)) {
224 set_error(error, line_no, "invalid label");
225 return -1;
226 }
227 if (pass == 1 && symbol_add(symbols, label, *pc, error, line_no) != 0) {
228 return -1;
229 }
230 s = trim(colon + 1);
231 }
232
233 if (*s == '\0') {
234 return 0;
235 }
236
237 char *mnemonic = strtok(s, " \t\r\n");
238 if (!mnemonic) {
239 return 0;
240 }
241
242 VmOpcode opcode;
243 int operand_count;
244 if (instruction_info(mnemonic, &opcode, &operand_count) != 0) {
245 set_error(error, line_no, "unknown mnemonic");
246 return -1;
247 }
248
249 char *operand_text = strtok(NULL, "");
250 int64_t operand = 0;
251 if (operand_count == 0) {
252 if (operand_text && *trim(operand_text) != '\0') {
253 set_error(error, line_no, "unexpected operand");
254 return -1;
255 }
256 } else {
257 if (!operand_text) {
258 set_error(error, line_no, "missing operand");
259 return -1;
260 }
261 if (parse_operand(operand_text, opcode, symbols, pass, &operand, error, line_no) != 0) {
262 return -1;
263 }
264 }
265
266 if (pass == 2) {
267 VmError err = bytecode_emit(out, opcode);
268 if (err != VM_OK) {
269 set_error(error, line_no, vm_error_string(err));
270 return -1;
271 }
272 if (operand_count == 1) {
273 err = bytecode_emit(out, operand);
274 if (err != VM_OK) {
275 set_error(error, line_no, vm_error_string(err));
276 return -1;
277 }
278 }
279 }
280 *pc += (size_t)(1 + operand_count);
281 return 0;
282}
283
284static int assemble_pass(const char *source, int pass, SymbolTable *symbols, Bytecode *out, AsmError *error) {
285 const char *start = source;
286 int line_no = 1;
287 size_t pc = 0;
288
289 while (*start) {
290 const char *end = strchr(start, '\n');
291 size_t len = end ? (size_t)(end - start) : strlen(start);
292 char *line = (char *)malloc(len + 1);
293 if (!line) {
294 set_error(error, line_no, "out of memory");
295 return -1;
296 }
297 memcpy(line, start, len);
298 line[len] = '\0';
299
300 int rc = process_line(line, line_no, pass, symbols, out, &pc, error);
301 free(line);
302 if (rc != 0) {
303 return -1;
304 }
305
306 if (!end) {
307 break;
308 }
309 start = end + 1;
310 line_no++;
311 }
312 return 0;
313}
314
315int assembler_compile_string(const char *source, Bytecode *out, AsmError *error) {
316 if (error) {
317 error->line = 0;
318 error->message[0] = '\0';
319 }
320 if (!source || !out) {
321 set_error(error, 0, "invalid assembler argument");
322 return -1;
323 }
324
325 SymbolTable symbols = {0};
326 if (assemble_pass(source, 1, &symbols, NULL, error) != 0) {
327 symbol_table_free(&symbols);
328 return -1;
329 }
330
331 bytecode_init(out);
332 if (assemble_pass(source, 2, &symbols, out, error) != 0) {
333 bytecode_free(out);
334 symbol_table_free(&symbols);
335 return -1;
336 }
337
338 symbol_table_free(&symbols);
339 return 0;
340}
341
342int assembler_compile_file(const char *path, Bytecode *out, AsmError *error) {
343 FILE *f = fopen(path, "rb");
344 if (!f) {
345 set_error(error, 0, "could not open input file");
346 return -1;
347 }
348
349 if (fseek(f, 0, SEEK_END) != 0) {
350 fclose(f);
351 set_error(error, 0, "could not seek input file");
352 return -1;
353 }
354 long size = ftell(f);
355 if (size < 0) {
356 fclose(f);
357 set_error(error, 0, "could not measure input file");
358 return -1;
359 }
360 if (fseek(f, 0, SEEK_SET) != 0) {
361 fclose(f);
362 set_error(error, 0, "could not rewind input file");
363 return -1;
364 }
365
366 char *source = (char *)malloc((size_t)size + 1);
367 if (!source) {
368 fclose(f);
369 set_error(error, 0, "out of memory");
370 return -1;
371 }
372 size_t read_count = fread(source, 1, (size_t)size, f);
373 fclose(f);
374 if (read_count != (size_t)size) {
375 free(source);
376 set_error(error, 0, "could not read input file");
377 return -1;
378 }
379 source[size] = '\0';
380
381 int rc = assembler_compile_string(source, out, error);
382 free(source);
383 return rc;
384}
385
Discussion
No comments yet. Start the discussion. Recorded by @patrick-toulme.