1#include "asm.h"
2#include "vm.h"
3
4#include <ctype.h>
5#include <errno.h>
6#include <stdarg.h>
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10
11typedef enum { OPERAND_NONE, OPERAND_IMM64, OPERAND_ADDR, OPERAND_LOCAL } OperandKind;
12
13typedef struct {
14 const char *name;
15 uint8_t opcode;
16 OperandKind operand;
17} InstrDef;
18
19static const InstrDef INSTR_TABLE[] = {
20 {"PUSH", OP_PUSH, OPERAND_IMM64},
21 {"POP", OP_POP, OPERAND_NONE},
22 {"DUP", OP_DUP, OPERAND_NONE},
23 {"SWAP", OP_SWAP, OPERAND_NONE},
24 {"ADD", OP_ADD, OPERAND_NONE},
25 {"SUB", OP_SUB, OPERAND_NONE},
26 {"MUL", OP_MUL, OPERAND_NONE},
27 {"DIV", OP_DIV, OPERAND_NONE},
28 {"MOD", OP_MOD, OPERAND_NONE},
29 {"NEG", OP_NEG, OPERAND_NONE},
30 {"EQ", OP_EQ, OPERAND_NONE},
31 {"LT", OP_LT, OPERAND_NONE},
32 {"GT", OP_GT, OPERAND_NONE},
33 {"JMP", OP_JMP, OPERAND_ADDR},
34 {"JZ", OP_JZ, OPERAND_ADDR},
35 {"JNZ", OP_JNZ, OPERAND_ADDR},
36 {"CALL", OP_CALL, OPERAND_ADDR},
37 {"RET", OP_RET, OPERAND_NONE},
38 {"LOAD", OP_LOAD, OPERAND_LOCAL},
39 {"STORE", OP_STORE, OPERAND_LOCAL},
40 {"PRINT", OP_PRINT, OPERAND_NONE},
41 {"HALT", OP_HALT, OPERAND_NONE},
42};
43#define INSTR_TABLE_LEN (sizeof(INSTR_TABLE) / sizeof(INSTR_TABLE[0]))
44
45typedef struct {
46 const InstrDef *instr;
47 char *operand;
48 int line_no;
49 size_t address;
50 size_t size;
51} Statement;
52
53typedef struct {
54 char *name;
55 size_t address;
56} LabelEntry;
57
58static char *xstrdup(const char *s) {
59 size_t n = strlen(s) + 1;
60 char *p = malloc(n);
61 if (p) memcpy(p, s, n);
62 return p;
63}
64
65static char *trim(char *s) {
66 while (*s != '\0' && isspace((unsigned char)*s)) s++;
67 if (*s == '\0') return s;
68 char *end = s + strlen(s) - 1;
69 while (end > s && isspace((unsigned char)*end)) {
70 *end = '\0';
71 end--;
72 }
73 return s;
74}
75
76static int ieq(const char *a, const char *b) {
77 while (*a != '\0' && *b != '\0') {
78 if (tolower((unsigned char)*a) != tolower((unsigned char)*b)) return 0;
79 a++;
80 b++;
81 }
82 return *a == '\0' && *b == '\0';
83}
84
85static int is_valid_label_name(const char *s) {
86 if (s[0] == '\0') return 0;
87 if (!isalpha((unsigned char)s[0]) && s[0] != '_') return 0;
88 for (const char *p = s + 1; *p != '\0'; p++) {
89 if (!isalnum((unsigned char)*p) && *p != '_') return 0;
90 }
91 return 1;
92}
93
94static const InstrDef *find_instr(const char *name) {
95 for (size_t i = 0; i < INSTR_TABLE_LEN; i++) {
96 if (ieq(name, INSTR_TABLE[i].name)) return &INSTR_TABLE[i];
97 }
98 return NULL;
99}
100
101static LabelEntry *find_label(LabelEntry *labels, size_t count, const char *name) {
102 for (size_t i = 0; i < count; i++) {
103 if (strcmp(labels[i].name, name) == 0) return &labels[i];
104 }
105 return NULL;
106}
107
108static void set_err(AsmError *err, AsmStatus status, int line, const char *fmt, ...) {
109 err->status = status;
110 err->line = line;
111 va_list ap;
112 va_start(ap, fmt);
113 vsnprintf(err->message, sizeof(err->message), fmt, ap);
114 va_end(ap);
115}
116
117static void free_statements(Statement *stmts, size_t count) {
118 for (size_t i = 0; i < count; i++) free(stmts[i].operand);
119 free(stmts);
120}
121
122static void free_labels(LabelEntry *labels, size_t count) {
123 for (size_t i = 0; i < count; i++) free(labels[i].name);
124 free(labels);
125}
126
127AsmStatus asm_assemble(const char *source, uint8_t **out_code, size_t *out_size, AsmError *err) {
128 AsmError local_err;
129 if (err == NULL) err = &local_err;
130 memset(err, 0, sizeof(*err));
131 err->status = ASM_OK;
132
133 *out_code = NULL;
134 *out_size = 0;
135
136 char *buf = xstrdup(source);
137 if (buf == NULL) {
138 set_err(err, ASM_ERR_OUT_OF_MEMORY, 0, "out of memory");
139 return err->status;
140 }
141
142 Statement *stmts = NULL;
143 size_t stmt_count = 0, stmt_cap = 0;
144 LabelEntry *labels = NULL;
145 size_t label_count = 0, label_cap = 0;
146 size_t offset = 0;
147 int line_no = 0;
148 AsmStatus rc = ASM_OK;
149
150 char *cursor = buf;
151 while (*cursor != '\0') {
152 line_no++;
153 char *nl = strchr(cursor, '\n');
154 if (nl != NULL) *nl = '\0';
155 char *line = cursor;
156 cursor = (nl != NULL) ? nl + 1 : cursor + strlen(cursor);
157
158 size_t len = strlen(line);
159 if (len > 0 && line[len - 1] == '\r') line[len - 1] = '\0';
160
161 for (char *p = line; *p != '\0'; p++) {
162 if (*p == ';' || *p == '#') {
163 *p = '\0';
164 break;
165 }
166 }
167
168 char *trimmed = trim(line);
169 if (*trimmed == '\0') continue;
170
171 char *label_name = NULL;
172 char *rest = trimmed;
173 char *colon = strchr(trimmed, ':');
174 if (colon != NULL) {
175 *colon = '\0';
176 char *lbl = trim(trimmed);
177 if (*lbl == '\0') {
178 rc = ASM_ERR_EMPTY_LABEL;
179 set_err(err, rc, line_no, "empty label name");
180 goto fail;
181 }
182 if (!is_valid_label_name(lbl)) {
183 rc = ASM_ERR_BAD_LABEL_NAME;
184 set_err(err, rc, line_no, "invalid label name '%s'", lbl);
185 goto fail;
186 }
187 label_name = lbl;
188 rest = trim(colon + 1);
189 }
190
191 const InstrDef *instr = NULL;
192 char *operand = NULL;
193 if (*rest != '\0') {
194 char *space = rest;
195 while (*space != '\0' && !isspace((unsigned char)*space)) space++;
196 size_t mnlen = (size_t)(space - rest);
197 char mnemonic_buf[32];
198 if (mnlen == 0 || mnlen >= sizeof(mnemonic_buf)) {
199 rc = ASM_ERR_UNKNOWN_MNEMONIC;
200 set_err(err, rc, line_no, "invalid mnemonic");
201 goto fail;
202 }
203 memcpy(mnemonic_buf, rest, mnlen);
204 mnemonic_buf[mnlen] = '\0';
205
206 instr = find_instr(mnemonic_buf);
207 if (instr == NULL) {
208 rc = ASM_ERR_UNKNOWN_MNEMONIC;
209 set_err(err, rc, line_no, "unknown mnemonic '%s'", mnemonic_buf);
210 goto fail;
211 }
212
213 char *operand_part = trim(space);
214 if (instr->operand == OPERAND_NONE) {
215 if (*operand_part != '\0') {
216 rc = ASM_ERR_UNEXPECTED_OPERAND;
217 set_err(err, rc, line_no, "%s takes no operand", instr->name);
218 goto fail;
219 }
220 } else {
221 if (*operand_part == '\0') {
222 rc = ASM_ERR_MISSING_OPERAND;
223 set_err(err, rc, line_no, "%s requires an operand", instr->name);
224 goto fail;
225 }
226 operand = xstrdup(operand_part);
227 if (operand == NULL) {
228 rc = ASM_ERR_OUT_OF_MEMORY;
229 set_err(err, rc, line_no, "out of memory");
230 goto fail;
231 }
232 }
233 }
234
235 size_t size = 0;
236 if (instr != NULL) {
237 switch (instr->operand) {
238 case OPERAND_NONE: size = 1; break;
239 case OPERAND_IMM64: size = 9; break;
240 case OPERAND_ADDR: size = 5; break;
241 case OPERAND_LOCAL: size = 2; break;
242 }
243 }
244
245 if (label_name != NULL) {
246 if (find_label(labels, label_count, label_name) != NULL) {
247 rc = ASM_ERR_DUPLICATE_LABEL;
248 set_err(err, rc, line_no, "duplicate label '%s'", label_name);
249 free(operand);
250 goto fail;
251 }
252 if (label_count == label_cap) {
253 size_t new_cap = label_cap == 0 ? 8 : label_cap * 2;
254 LabelEntry *nl = realloc(labels, new_cap * sizeof(LabelEntry));
255 if (nl == NULL) {
256 rc = ASM_ERR_OUT_OF_MEMORY;
257 set_err(err, rc, line_no, "out of memory");
258 free(operand);
259 goto fail;
260 }
261 labels = nl;
262 label_cap = new_cap;
263 }
264 labels[label_count].name = xstrdup(label_name);
265 labels[label_count].address = offset;
266 label_count++;
267 }
268
269 if (instr != NULL) {
270 if (stmt_count == stmt_cap) {
271 size_t new_cap = stmt_cap == 0 ? 16 : stmt_cap * 2;
272 Statement *ns = realloc(stmts, new_cap * sizeof(Statement));
273 if (ns == NULL) {
274 rc = ASM_ERR_OUT_OF_MEMORY;
275 set_err(err, rc, line_no, "out of memory");
276 free(operand);
277 goto fail;
278 }
279 stmts = ns;
280 stmt_cap = new_cap;
281 }
282 stmts[stmt_count].instr = instr;
283 stmts[stmt_count].operand = operand;
284 stmts[stmt_count].line_no = line_no;
285 stmts[stmt_count].address = offset;
286 stmts[stmt_count].size = size;
287 stmt_count++;
288 offset += size;
289 }
290 }
291
292
293 {
294 uint8_t *code = NULL;
295 if (offset > 0) {
296 code = malloc(offset);
297 if (code == NULL) {
298 rc = ASM_ERR_OUT_OF_MEMORY;
299 set_err(err, rc, 0, "out of memory");
300 goto fail;
301 }
302 }
303
304 for (size_t i = 0; i < stmt_count; i++) {
305 Statement *s = &stmts[i];
306 code[s->address] = s->instr->opcode;
307 switch (s->instr->operand) {
308 case OPERAND_NONE:
309 break;
310
311 case OPERAND_IMM64: {
312 char *endptr = NULL;
313 errno = 0;
314 long long v = strtoll(s->operand, &endptr, 0);
315 if (endptr == s->operand || *endptr != '\0' || errno == ERANGE) {
316 rc = ASM_ERR_BAD_OPERAND;
317 set_err(err, rc, s->line_no, "invalid integer operand '%s'", s->operand);
318 free(code);
319 goto fail;
320 }
321 int64_t val = (int64_t)v;
322 memcpy(code + s->address + 1, &val, sizeof(val));
323 break;
324 }
325
326 case OPERAND_ADDR: {
327 LabelEntry *le = find_label(labels, label_count, s->operand);
328 if (le == NULL) {
329 rc = ASM_ERR_UNKNOWN_LABEL;
330 set_err(err, rc, s->line_no, "undefined label '%s'", s->operand);
331 free(code);
332 goto fail;
333 }
334 if (le->address > (size_t)INT32_MAX) {
335 rc = ASM_ERR_BAD_OPERAND;
336 set_err(err, rc, s->line_no, "target address too large for '%s'", s->operand);
337 free(code);
338 goto fail;
339 }
340 int32_t addr32 = (int32_t)le->address;
341 memcpy(code + s->address + 1, &addr32, sizeof(addr32));
342 break;
343 }
344
345 case OPERAND_LOCAL: {
346 char *endptr = NULL;
347 errno = 0;
348 long v = strtol(s->operand, &endptr, 10);
349 if (endptr == s->operand || *endptr != '\0' || errno == ERANGE || v < 0 ||
350 v >= VM_LOCALS_PER_FRAME) {
351 rc = ASM_ERR_BAD_OPERAND;
352 set_err(err, rc, s->line_no, "local index must be 0-%d, got '%s'",
353 VM_LOCALS_PER_FRAME - 1, s->operand);
354 free(code);
355 goto fail;
356 }
357 code[s->address + 1] = (uint8_t)v;
358 break;
359 }
360 }
361 }
362
363 *out_code = code;
364 *out_size = offset;
365 }
366
367 free_statements(stmts, stmt_count);
368 free_labels(labels, label_count);
369 free(buf);
370 return ASM_OK;
371
372fail:
373 free_statements(stmts, stmt_count);
374 free_labels(labels, label_count);
375 free(buf);
376 return rc;
377}
378
379const char *asm_status_str(AsmStatus status) {
380 switch (status) {
381 case ASM_OK: return "ok";
382 case ASM_ERR_EMPTY_LABEL: return "empty label";
383 case ASM_ERR_BAD_LABEL_NAME: return "invalid label name";
384 case ASM_ERR_DUPLICATE_LABEL: return "duplicate label";
385 case ASM_ERR_UNKNOWN_MNEMONIC: return "unknown mnemonic";
386 case ASM_ERR_UNKNOWN_LABEL: return "undefined label";
387 case ASM_ERR_MISSING_OPERAND: return "missing operand";
388 case ASM_ERR_UNEXPECTED_OPERAND: return "unexpected operand";
389 case ASM_ERR_BAD_OPERAND: return "bad operand";
390 case ASM_ERR_OUT_OF_MEMORY: return "out of memory";
391 default: return "unknown error";
392 }
393}
394
Discussion
No comments yet. Start the discussion. Recorded by @patrick-toulme.