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 return 0;
173 }
174
175 if (!needs_label_resolution) {
176 set_error(error, line, "operand must be an integer");
177 return -1;
178 }
179
180 if (!valid_label_name(tok)) {
181 set_error(error, line, "invalid label reference");
182 return -1;
183 }
184
185 if (pass == 1) {
186 *operand = 0;
187 return 0;
188 }
189
190 Symbol *symbol = symbol_find(symbols, tok);
191 if (!symbol) {
192 set_error(error, line, "undefined label");
193 return -1;
194 }
195 *operand = (int64_t)symbol->address;
196 return 0;
197}
198
199static int process_line(char *line, int line_no, int pass, SymbolTable *symbols,
200 Bytecode *out, size_t *pc, AsmError *error) {
201 for (char *p = line; *p; p++) {
202 if (*p == ';' || *p == '#') {
203 *p = '\0';
204 break;
205 }
206 if (*p == ',') {
207 *p = ' ';
208 }
209 }
210
211 char *s = trim(line);
212 while (*s) {
213 char *colon = strchr(s, ':');
214 if (!colon) {
215 break;
216 }
217 *colon = '\0';
218 char *label = trim(s);
219 if (!valid_label_name(label)) {
220 set_error(error, line_no, "invalid label");
221 return -1;
222 }
223 if (pass == 1 && symbol_add(symbols, label, *pc, error, line_no) != 0) {
224 return -1;
225 }
226 s = trim(colon + 1);
227 }
228
229 if (*s == '\0') {
230 return 0;
231 }
232
233 char *mnemonic = strtok(s, " \t\r\n");
234 if (!mnemonic) {
235 return 0;
236 }
237
238 VmOpcode opcode;
239 int operand_count;
240 if (instruction_info(mnemonic, &opcode, &operand_count) != 0) {
241 set_error(error, line_no, "unknown mnemonic");
242 return -1;
243 }
244
245 char *operand_text = strtok(NULL, "");
246 int64_t operand = 0;
247 if (operand_count == 0) {
248 if (operand_text && *trim(operand_text) != '\0') {
249 set_error(error, line_no, "unexpected operand");
250 return -1;
251 }
− } else if (parse_operand(operand_text ? operand_text : (char *)"", opcode, symbols, pass, &operand, error, line_no) != 0) {
− return -1;
252 } else {
253 if (!operand_text) {
254 set_error(error, line_no, "missing operand");
255 return -1;
256 }
257 if (parse_operand(operand_text, opcode, symbols, pass, &operand, error, line_no) != 0) {
258 return -1;
259 }
260 }
261
262 if (pass == 2) {
263 VmError err = bytecode_emit(out, opcode);
264 if (err != VM_OK) {
265 set_error(error, line_no, vm_error_string(err));
266 return -1;
267 }
268 if (operand_count == 1) {
269 err = bytecode_emit(out, operand);
270 if (err != VM_OK) {
271 set_error(error, line_no, vm_error_string(err));
272 return -1;
273 }
274 }
275 }
276 *pc += (size_t)(1 + operand_count);
277 return 0;
278}
279
280static int assemble_pass(const char *source, int pass, SymbolTable *symbols, Bytecode *out, AsmError *error) {
281 const char *start = source;
282 int line_no = 1;
283 size_t pc = 0;
284
285 while (*start) {
286 const char *end = strchr(start, '\n');
287 size_t len = end ? (size_t)(end - start) : strlen(start);
288 char *line = (char *)malloc(len + 1);
289 if (!line) {
290 set_error(error, line_no, "out of memory");
291 return -1;
292 }
293 memcpy(line, start, len);
294 line[len] = '\0';
295
296 int rc = process_line(line, line_no, pass, symbols, out, &pc, error);
297 free(line);
298 if (rc != 0) {
299 return -1;
300 }
301
302 if (!end) {
303 break;
304 }
305 start = end + 1;
306 line_no++;
307 }
308 return 0;
309}
310
311int assembler_compile_string(const char *source, Bytecode *out, AsmError *error) {
312 if (error) {
313 error->line = 0;
314 error->message[0] = '\0';
315 }
316 if (!source || !out) {
317 set_error(error, 0, "invalid assembler argument");
318 return -1;
319 }
320
321 SymbolTable symbols = {0};
322 if (assemble_pass(source, 1, &symbols, NULL, error) != 0) {
323 symbol_table_free(&symbols);
324 return -1;
325 }
326
327 bytecode_init(out);
328 if (assemble_pass(source, 2, &symbols, out, error) != 0) {
329 bytecode_free(out);
330 symbol_table_free(&symbols);
331 return -1;
332 }
333
334 symbol_table_free(&symbols);
335 return 0;
336}
337
338int assembler_compile_file(const char *path, Bytecode *out, AsmError *error) {
339 FILE *f = fopen(path, "rb");
340 if (!f) {
341 set_error(error, 0, "could not open input file");
342 return -1;
343 }
344
345 if (fseek(f, 0, SEEK_END) != 0) {
346 fclose(f);
347 set_error(error, 0, "could not seek input file");
348 return -1;
349 }
350 long size = ftell(f);
351 if (size < 0) {
352 fclose(f);
353 set_error(error, 0, "could not measure input file");
354 return -1;
355 }
356 if (fseek(f, 0, SEEK_SET) != 0) {
357 fclose(f);
358 set_error(error, 0, "could not rewind input file");
359 return -1;
360 }
361
362 char *source = (char *)malloc((size_t)size + 1);
363 if (!source) {
364 fclose(f);
365 set_error(error, 0, "out of memory");
366 return -1;
367 }
368 size_t read_count = fread(source, 1, (size_t)size, f);
369 fclose(f);
370 if (read_count != (size_t)size) {
371 free(source);
372 set_error(error, 0, "could not read input file");
373 return -1;
374 }
375 source[size] = '\0';
376
377 int rc = assembler_compile_string(source, out, error);
378 free(source);
379 return rc;
380}
381
Discussion
No comments yet. Start the discussion. Recorded by @patrick-toulme.