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 {
10 char name[64];
11 size_t address;
12} Label;
13
14typedef struct {
15 Label labels[512];
16 size_t count;
17} LabelTable;
18
19typedef struct {
20 const char *name;
21 int opcode;
22 int operands;
23} OpInfo;
24
25static const OpInfo OPS[] = {
26 {"PUSH", OP_PUSH, 1}, {"POP", OP_POP, 0}, {"DUP", OP_DUP, 0}, {"SWAP", OP_SWAP, 0},
27 {"ADD", OP_ADD, 0}, {"SUB", OP_SUB, 0}, {"MUL", OP_MUL, 0}, {"DIV", OP_DIV, 0},
28 {"MOD", OP_MOD, 0}, {"NEG", OP_NEG, 0}, {"EQ", OP_EQ, 0}, {"LT", OP_LT, 0},
29 {"GT", OP_GT, 0}, {"JMP", OP_JMP, 1}, {"JZ", OP_JZ, 1}, {"JNZ", OP_JNZ, 1},
30 {"CALL", OP_CALL, 1}, {"RET", OP_RET, 0}, {"LOAD", OP_LOAD, 1}, {"STORE", OP_STORE, 1},
31 {"PRINT", OP_PRINT, 0}, {"HALT", OP_HALT, 0}
32};
33
34void assembler_init(Assembler *assembler) {
35 assembler->error[0] = '\0';
36}
37
38static void asm_error(Assembler *assembler, int line, const char *message) {
39 snprintf(assembler->error, sizeof(assembler->error), "line %d: %s", line, message);
40}
41
42static char *trim(char *s) {
43 char *end;
44 while (isspace((unsigned char)*s)) s++;
45 if (*s == '\0') return s;
46 end = s + strlen(s) - 1;
47 while (end > s && isspace((unsigned char)*end)) *end-- = '\0';
48 return s;
49}
50
51static void uppercase(char *s) {
52 while (*s) {
53 *s = (char)toupper((unsigned char)*s);
54 s++;
55 }
56}
57
58static const OpInfo *find_op(const char *name) {
59 size_t i;
60 for (i = 0; i < sizeof(OPS) / sizeof(OPS[0]); i++) {
61 if (strcmp(OPS[i].name, name) == 0) return &OPS[i];
62 }
63 return NULL;
64}
65
66static int parse_int(const char *s, int *value) {
67 char *end;
68 long parsed;
69 errno = 0;
70 parsed = strtol(s, &end, 10);
71 if (errno || end == s || *end != '\0' || parsed < -2147483647L - 1L || parsed > 2147483647L) return 0;
72 *value = (int)parsed;
73 return 1;
74}
75
76static int valid_label_name(const char *s) {
77 if (!isalpha((unsigned char)s[0]) && s[0] != '_') return 0;
78 s++;
79 while (*s) {
80 if (!isalnum((unsigned char)*s) && *s != '_') return 0;
81 s++;
82 }
83 return 1;
84}
85
86static int add_label(Assembler *assembler, LabelTable *labels, const char *name, size_t address, int line) {
87 size_t i;
88 if (!valid_label_name(name)) {
89 asm_error(assembler, line, "invalid label name");
90 return 0;
91 }
92 for (i = 0; i < labels->count; i++) {
93 if (strcmp(labels->labels[i].name, name) == 0) {
94 asm_error(assembler, line, "duplicate label");
95 return 0;
96 }
97 }
98 if (labels->count >= sizeof(labels->labels) / sizeof(labels->labels[0])) {
99 asm_error(assembler, line, "too many labels");
100 return 0;
101 }
102 snprintf(labels->labels[labels->count].name, sizeof(labels->labels[labels->count].name), "%s", name);
103 labels->labels[labels->count].address = address;
104 labels->count++;
105 return 1;
106}
107
108static int find_label(const LabelTable *labels, const char *name, size_t *address) {
109 size_t i;
110 for (i = 0; i < labels->count; i++) {
111 if (strcmp(labels->labels[i].name, name) == 0) {
112 *address = labels->labels[i].address;
113 return 1;
114 }
115 }
116 return 0;
117}
118
119static void strip_comment(char *line) {
120 char *comment = strchr(line, ';');
121 if (comment) *comment = '\0';
122 comment = strchr(line, '#');
123 if (comment) *comment = '\0';
124}
125
126static int next_line(char **cursor, char *line, size_t line_size) {
127 size_t len = 0;
128 if (**cursor == '\0') return 0;
129 while ((*cursor)[len] && (*cursor)[len] != '\n' && len + 1 < line_size) {
130 line[len] = (*cursor)[len];
131 len++;
132 }
133 line[len] = '\0';
134 while ((*cursor)[len] && (*cursor)[len] != '\n') len++;
135 *cursor += len;
136 if (**cursor == '\n') (*cursor)++;
137 return 1;
138}
139
140static int first_pass(Assembler *assembler, char *source, LabelTable *labels) {
141 char *cursor = source;
142 char line[512];
143 int line_no = 0;
144 size_t pc = 0;
145
146 while (next_line(&cursor, line, sizeof(line))) {
147 char *work;
148 char *colon;
149 char *mnemonic;
150 const OpInfo *op;
151 line_no++;
152 strip_comment(line);
153 work = trim(line);
154 if (*work == '\0') continue;
155
156 colon = strchr(work, ':');
157 if (colon) {
158 *colon = '\0';
159 if (!add_label(assembler, labels, trim(work), pc, line_no)) return 0;
160 work = trim(colon + 1);
161 if (*work == '\0') continue;
162 }
163
164 mnemonic = strtok(work, " \t\r");
165 if (!mnemonic) continue;
166 uppercase(mnemonic);
167 op = find_op(mnemonic);
168 if (!op) {
169 asm_error(assembler, line_no, "unknown mnemonic");
170 return 0;
171 }
172 pc += (size_t)(1 + op->operands);
173 if (pc > sizeof(((Program *)0)->code) / sizeof(((Program *)0)->code[0])) {
174 asm_error(assembler, line_no, "program too large");
175 return 0;
176 }
177 }
178 return 1;
179}
180
181static int emit(Assembler *assembler, Program *program, int value, int line) {
182 if (program->count >= sizeof(program->code) / sizeof(program->code[0])) {
183 asm_error(assembler, line, "program too large");
184 return 0;
185 }
186 program->code[program->count++] = value;
187 return 1;
188}
189
190static int needs_label_operand(int opcode) {
191 return opcode == OP_JMP || opcode == OP_JZ || opcode == OP_JNZ || opcode == OP_CALL;
192}
193
194static int second_pass(Assembler *assembler, char *source, const LabelTable *labels, Program *program) {
195 char *cursor = source;
196 char line[512];
197 int line_no = 0;
198 program->count = 0;
199
200 while (next_line(&cursor, line, sizeof(line))) {
201 char *work;
202 char *colon;
203 char *mnemonic;
204 char *operand_text;
205 char *extra;
206 const OpInfo *op;
207 int operand = 0;
208 size_t address;
209
210 line_no++;
211 strip_comment(line);
212 work = trim(line);
213 if (*work == '\0') continue;
214 colon = strchr(work, ':');
215 if (colon) {
216 work = trim(colon + 1);
217 if (*work == '\0') continue;
218 }
219
220 mnemonic = strtok(work, " \t\r");
221 if (!mnemonic) continue;
222 uppercase(mnemonic);
223 op = find_op(mnemonic);
224 if (!op) {
225 asm_error(assembler, line_no, "unknown mnemonic");
226 return 0;
227 }
228 if (!emit(assembler, program, op->opcode, line_no)) return 0;
229
230 operand_text = strtok(NULL, " \t\r");
231 extra = strtok(NULL, " \t\r");
232 if ((op->operands == 0 && operand_text) || (op->operands == 1 && (!operand_text || extra))) {
233 asm_error(assembler, line_no, "wrong operand count");
234 return 0;
235 }
236 if (op->operands == 1) {
237 if (needs_label_operand(op->opcode) && find_label(labels, operand_text, &address)) {
238 operand = (int)address;
239 } else if (!parse_int(operand_text, &operand)) {
240 asm_error(assembler, line_no, "invalid operand");
241 return 0;
242 }
243 if ((op->opcode == OP_LOAD || op->opcode == OP_STORE) && (operand < 0 || operand >= 16)) {
244 asm_error(assembler, line_no, "local index out of range");
245 return 0;
246 }
247 if (!emit(assembler, program, operand, line_no)) return 0;
248 }
249 }
250 return 1;
251}
252
253int assembler_assemble_string(Assembler *assembler, const char *source, Program *program) {
254 char *copy1;
255 char *copy2;
256 LabelTable labels;
257 int ok;
258
259 assembler_init(assembler);
260 memset(&labels, 0, sizeof(labels));
261 copy1 = malloc(strlen(source) + 1);
262 copy2 = malloc(strlen(source) + 1);
263 if (!copy1 || !copy2) {
264 snprintf(assembler->error, sizeof(assembler->error), "out of memory");
265 free(copy1);
266 free(copy2);
267 return 0;
268 }
269 strcpy(copy1, source);
270 strcpy(copy2, source);
271
272 ok = first_pass(assembler, copy1, &labels) && second_pass(assembler, copy2, &labels, program);
273 free(copy1);
274 free(copy2);
275 return ok;
276}
277
278int assembler_assemble_file(Assembler *assembler, const char *path, Program *program) {
279 FILE *file = fopen(path, "rb");
280 long size;
281 char *source;
282 int ok;
283 if (!file) {
284 snprintf(assembler->error, sizeof(assembler->error), "cannot open %s", path);
285 return 0;
286 }
287 if (fseek(file, 0, SEEK_END) != 0) {
288 fclose(file);
289 snprintf(assembler->error, sizeof(assembler->error), "cannot seek %s", path);
290 return 0;
291 }
292 size = ftell(file);
293 if (size < 0) {
294 fclose(file);
295 snprintf(assembler->error, sizeof(assembler->error), "cannot read size of %s", path);
296 return 0;
297 }
298 rewind(file);
299 source = malloc((size_t)size + 1);
300 if (!source) {
301 fclose(file);
302 snprintf(assembler->error, sizeof(assembler->error), "out of memory");
303 return 0;
304 }
305 if (fread(source, 1, (size_t)size, file) != (size_t)size) {
306 free(source);
307 fclose(file);
308 snprintf(assembler->error, sizeof(assembler->error), "cannot read %s", path);
309 return 0;
310 }
311 source[size] = '\0';
312 fclose(file);
313 ok = assembler_assemble_string(assembler, source, program);
314 free(source);
315 return ok;
316}
317
Discussion
No comments yet. Start the discussion. Recorded by @patrick-toulme.