1#include "asm.h"
2#include <stdio.h>
3#include <stdlib.h>
4#include <string.h>
5#include <ctype.h>
6#include <strings.h>
7
8typedef enum {
9 ARG_NONE,
10 ARG_INT64,
11 ARG_LOCAL,
12 ARG_JUMP
13} ArgType;
14
15typedef struct {
16 const char *mnemonic;
17 Opcode opcode;
18 ArgType arg_type;
19} OpcodeInfo;
20
21static const OpcodeInfo OPCODES[] = {
22 {"HALT", OP_HALT, ARG_NONE},
23 {"PUSH", OP_PUSH, ARG_INT64},
24 {"POP", OP_POP, ARG_NONE},
25 {"DUP", OP_DUP, ARG_NONE},
26 {"SWAP", OP_SWAP, ARG_NONE},
27 {"ADD", OP_ADD, ARG_NONE},
28 {"SUB", OP_SUB, ARG_NONE},
29 {"MUL", OP_MUL, ARG_NONE},
30 {"DIV", OP_DIV, ARG_NONE},
31 {"MOD", OP_MOD, ARG_NONE},
32 {"NEG", OP_NEG, ARG_NONE},
33 {"EQ", OP_EQ, ARG_NONE},
34 {"LT", OP_LT, ARG_NONE},
35 {"GT", OP_GT, ARG_NONE},
36 {"JMP", OP_JMP, ARG_JUMP},
37 {"JZ", OP_JZ, ARG_JUMP},
38 {"JNZ", OP_JNZ, ARG_JUMP},
39 {"CALL", OP_CALL, ARG_JUMP},
40 {"RET", OP_RET, ARG_NONE},
41 {"LOAD", OP_LOAD, ARG_LOCAL},
42 {"STORE", OP_STORE, ARG_LOCAL},
43 {"PRINT", OP_PRINT, ARG_NONE},
44 {NULL, 0, ARG_NONE}
45};
46
47typedef struct {
48 char name[64];
49 uint32_t offset;
50} Label;
51
52#define MAX_LABELS 512
53
54typedef struct {
55 Label labels[MAX_LABELS];
56 size_t count;
57} SymbolTable;
58
59static void trim(char *s) {
60 if (!s) return;
61 char *p = s;
62 while (isspace((unsigned char)*p)) p++;
63 if (p != s) memmove(s, p, strlen(p) + 1);
64 size_t len = strlen(s);
65 while (len > 0 && isspace((unsigned char)s[len - 1])) {
66 s[--len] = '\0';
67 }
68}
69
70static void strip_comment(char *s) {
71 if (!s) return;
72 for (size_t i = 0; s[i] != '\0'; i++) {
73 if (s[i] == '#' || s[i] == ';') {
74 s[i] = '\0';
75 break;
76 }
77 if (s[i] == '/' && s[i+1] == '/') {
78 s[i] = '\0';
79 break;
80 }
81 }
82}
83
84static bool is_valid_label_name(const char *name) {
85 if (!name || *name == '\0') return false;
86 if (!isalpha((unsigned char)*name) && *name != '_') return false;
87 for (size_t i = 1; name[i] != '\0'; i++) {
88 if (!isalnum((unsigned char)name[i]) && name[i] != '_') return false;
89 }
90 return true;
91}
92
93static bool is_number(const char *str) {
94 if (!str || *str == '\0') return false;
95 if (*str == '+' || *str == '-') str++;
96 if (*str == '\0') return false;
97 if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) {
98 const char *p = str + 2;
99 if (*p == '\0') return false;
100 while (*p) {
101 if (!isxdigit((unsigned char)*p)) return false;
102 p++;
103 }
104 return true;
105 }
106 while (*str) {
107 if (!isdigit((unsigned char)*str)) return false;
108 str++;
109 }
110 return true;
111}
112
113static const OpcodeInfo *find_opcode(const char *name) {
114 if (!name) return NULL;
115 for (size_t i = 0; OPCODES[i].mnemonic != NULL; i++) {
116 if (strcasecmp(OPCODES[i].mnemonic, name) == 0) {
117 return &OPCODES[i];
118 }
119 }
120 return NULL;
121}
122
123static bool add_label(SymbolTable *syms, const char *name, uint32_t offset) {
124 if (syms->count >= MAX_LABELS) return false;
125 for (size_t i = 0; i < syms->count; i++) {
126 if (strcmp(syms->labels[i].name, name) == 0) {
127 return false;
128 }
129 }
130 strncpy(syms->labels[syms->count].name, name, sizeof(syms->labels[syms->count].name) - 1);
131 syms->labels[syms->count].name[sizeof(syms->labels[syms->count].name) - 1] = '\0';
132 syms->labels[syms->count].offset = offset;
133 syms->count++;
134 return true;
135}
136
137static int find_label(const SymbolTable *syms, const char *name) {
138 for (size_t i = 0; i < syms->count; i++) {
139 if (strcmp(syms->labels[i].name, name) == 0) {
140 return (int)i;
141 }
142 }
143 return -1;
144}
145
146static void set_error(AsmError *err, AsmResult status, int line, const char *msg) {
147 if (!err) return;
148 err->status = status;
149 err->error_line = line;
150 if (msg) {
151 strncpy(err->error_msg, msg, sizeof(err->error_msg) - 1);
152 err->error_msg[sizeof(err->error_msg) - 1] = '\0';
153 } else {
154 err->error_msg[0] = '\0';
155 }
156}
157
158static bool append_bytes(uint8_t **buf, size_t *size, size_t *capacity, const void *data, size_t n) {
159 if (*size + n > *capacity) {
160 size_t new_cap = (*capacity == 0) ? 256 : *capacity * 2;
161 while (new_cap < *size + n) new_cap *= 2;
162 uint8_t *new_buf = realloc(*buf, new_cap);
163 if (!new_buf) return false;
164 *buf = new_buf;
165 *capacity = new_cap;
166 }
167 memcpy(*buf + *size, data, n);
168 *size += n;
169 return true;
170}
171
172AsmResult assemble_string(const char *source, uint8_t **out_code, size_t *out_size, AsmError *err) {
173 if (!source || !out_code || !out_size) {
174 set_error(err, ASM_ERR_SYNTAX_ERROR, 0, "Invalid NULL parameter");
175 return ASM_ERR_SYNTAX_ERROR;
176 }
177
178 SymbolTable syms = { .count = 0 };
179
180
181 uint32_t current_offset = 0;
182 int line_num = 0;
183 const char *line_start = source;
184
185 while (*line_start != '\0') {
186 line_num++;
187 const char *line_end = strchr(line_start, '\n');
188 size_t line_len = line_end ? (size_t)(line_end - line_start) : strlen(line_start);
189
190 char linebuf[512];
191 if (line_len >= sizeof(linebuf)) {
192 set_error(err, ASM_ERR_SYNTAX_ERROR, line_num, "Line too long");
193 return ASM_ERR_SYNTAX_ERROR;
194 }
195 memcpy(linebuf, line_start, line_len);
196 linebuf[line_len] = '\0';
197
198 strip_comment(linebuf);
199 trim(linebuf);
200
201 if (linebuf[0] != '\0') {
202 char *colon = strchr(linebuf, ':');
203 char *inst_str = linebuf;
204
205 if (colon) {
206 *colon = '\0';
207 char *label_name = linebuf;
208 trim(label_name);
209
210 if (!is_valid_label_name(label_name)) {
211 set_error(err, ASM_ERR_SYNTAX_ERROR, line_num, "Invalid label name");
212 return ASM_ERR_SYNTAX_ERROR;
213 }
214
215 if (!add_label(&syms, label_name, current_offset)) {
216 set_error(err, ASM_ERR_DUPLICATE_LABEL, line_num, "Duplicate label definition");
217 return ASM_ERR_DUPLICATE_LABEL;
218 }
219
220 inst_str = colon + 1;
221 trim(inst_str);
222 }
223
224 if (inst_str[0] != '\0') {
225 char op_token[64] = {0};
226 char arg_token[128] = {0};
227 int scanned = sscanf(inst_str, "%63s %127s", op_token, arg_token);
228
229 if (scanned >= 1) {
230 const OpcodeInfo *info = find_opcode(op_token);
231 if (!info) {
232 set_error(err, ASM_ERR_UNKNOWN_INSTRUCTION, line_num, "Unknown instruction");
233 return ASM_ERR_UNKNOWN_INSTRUCTION;
234 }
235
236 if (info->arg_type != ARG_NONE && scanned < 2) {
237 set_error(err, ASM_ERR_INVALID_OPERAND, line_num, "Missing required operand");
238 return ASM_ERR_INVALID_OPERAND;
239 }
240
241 switch (info->arg_type) {
242 case ARG_NONE: current_offset += 1; break;
243 case ARG_INT64: current_offset += 9; break;
244 case ARG_LOCAL: current_offset += 2; break;
245 case ARG_JUMP: current_offset += 5; break;
246 }
247 }
248 }
249 }
250
251 line_start = line_end ? line_end + 1 : line_start + line_len;
252 }
253
254
255 uint8_t *code = NULL;
256 size_t code_size = 0;
257 size_t code_cap = 0;
258
259 line_num = 0;
260 line_start = source;
261
262 while (*line_start != '\0') {
263 line_num++;
264 const char *line_end = strchr(line_start, '\n');
265 size_t line_len = line_end ? (size_t)(line_end - line_start) : strlen(line_start);
266
267 char linebuf[512];
268 memcpy(linebuf, line_start, line_len);
269 linebuf[line_len] = '\0';
270
271 strip_comment(linebuf);
272 trim(linebuf);
273
274 if (linebuf[0] != '\0') {
275 char *colon = strchr(linebuf, ':');
276 char *inst_str = linebuf;
277
278 if (colon) {
279 inst_str = colon + 1;
280 trim(inst_str);
281 }
282
283 if (inst_str[0] != '\0') {
284 char op_token[64] = {0};
285 char arg_token[128] = {0};
286 int scanned = sscanf(inst_str, "%63s %127s", op_token, arg_token);
287
288 if (scanned >= 1) {
289 const OpcodeInfo *info = find_opcode(op_token);
290 uint8_t op_byte = (uint8_t)info->opcode;
291
292 if (!append_bytes(&code, &code_size, &code_cap, &op_byte, 1)) {
293 free(code);
294 set_error(err, ASM_ERR_OUT_OF_MEMORY, line_num, "Out of memory");
295 return ASM_ERR_OUT_OF_MEMORY;
296 }
297
298 if (info->arg_type == ARG_INT64) {
299 char *endptr;
300 int64_t val = (int64_t)strtoll(arg_token, &endptr, 0);
301 if (*endptr != '\0') {
302 free(code);
303 set_error(err, ASM_ERR_INVALID_OPERAND, line_num, "Invalid integer operand");
304 return ASM_ERR_INVALID_OPERAND;
305 }
306 if (!append_bytes(&code, &code_size, &code_cap, &val, 8)) {
307 free(code);
308 set_error(err, ASM_ERR_OUT_OF_MEMORY, line_num, "Out of memory");
309 return ASM_ERR_OUT_OF_MEMORY;
310 }
311 } else if (info->arg_type == ARG_LOCAL) {
312 char *endptr;
313 long val = strtol(arg_token, &endptr, 0);
314 if (*endptr != '\0' || val < 0 || val >= VM_LOCALS_COUNT) {
315 free(code);
316 set_error(err, ASM_ERR_INVALID_OPERAND, line_num, "Invalid local variable index");
317 return ASM_ERR_INVALID_OPERAND;
318 }
319 uint8_t uval = (uint8_t)val;
320 if (!append_bytes(&code, &code_size, &code_cap, &uval, 1)) {
321 free(code);
322 set_error(err, ASM_ERR_OUT_OF_MEMORY, line_num, "Out of memory");
323 return ASM_ERR_OUT_OF_MEMORY;
324 }
325 } else if (info->arg_type == ARG_JUMP) {
326 uint32_t target_offset = 0;
327 if (is_number(arg_token)) {
328 char *endptr;
329 target_offset = (uint32_t)strtoul(arg_token, &endptr, 0);
330 if (*endptr != '\0') {
331 free(code);
332 set_error(err, ASM_ERR_INVALID_OPERAND, line_num, "Invalid jump offset");
333 return ASM_ERR_INVALID_OPERAND;
334 }
335 } else {
336 int idx = find_label(&syms, arg_token);
337 if (idx < 0) {
338 free(code);
339 set_error(err, ASM_ERR_UNDEFINED_LABEL, line_num, "Undefined label");
340 return ASM_ERR_UNDEFINED_LABEL;
341 }
342 target_offset = syms.labels[idx].offset;
343 }
344 if (!append_bytes(&code, &code_size, &code_cap, &target_offset, 4)) {
345 free(code);
346 set_error(err, ASM_ERR_OUT_OF_MEMORY, line_num, "Out of memory");
347 return ASM_ERR_OUT_OF_MEMORY;
348 }
349 }
350 }
351 }
352 }
353
354 line_start = line_end ? line_end + 1 : line_start + line_len;
355 }
356
357 *out_code = code;
358 *out_size = code_size;
359 set_error(err, ASM_OK, 0, NULL);
360 return ASM_OK;
361}
362
363AsmResult assemble_file(const char *filename, uint8_t **out_code, size_t *out_size, AsmError *err) {
364 if (!filename) {
365 set_error(err, ASM_ERR_SYNTAX_ERROR, 0, "NULL filename");
366 return ASM_ERR_SYNTAX_ERROR;
367 }
368
369 FILE *f = fopen(filename, "rb");
370 if (!f) {
371 set_error(err, ASM_ERR_SYNTAX_ERROR, 0, "Could not open file");
372 return ASM_ERR_SYNTAX_ERROR;
373 }
374
375 fseek(f, 0, SEEK_END);
376 long len = ftell(f);
377 fseek(f, 0, SEEK_SET);
378
379 if (len < 0) {
380 fclose(f);
381 set_error(err, ASM_ERR_SYNTAX_ERROR, 0, "File seek error");
382 return ASM_ERR_SYNTAX_ERROR;
383 }
384
385 char *buf = malloc((size_t)len + 1);
386 if (!buf) {
387 fclose(f);
388 set_error(err, ASM_ERR_OUT_OF_MEMORY, 0, "Out of memory");
389 return ASM_ERR_OUT_OF_MEMORY;
390 }
391
392 size_t read_bytes = fread(buf, 1, (size_t)len, f);
393 fclose(f);
394 buf[read_bytes] = '\0';
395
396 AsmResult res = assemble_string(buf, out_code, out_size, err);
397 free(buf);
398 return res;
399}
400
401const char *asm_result_to_string(AsmResult result) {
402 switch (result) {
403 case ASM_OK: return "OK";
404 case ASM_ERR_UNKNOWN_INSTRUCTION: return "Unknown instruction";
405 case ASM_ERR_INVALID_OPERAND: return "Invalid operand";
406 case ASM_ERR_UNDEFINED_LABEL: return "Undefined label";
407 case ASM_ERR_DUPLICATE_LABEL: return "Duplicate label";
408 case ASM_ERR_SYNTAX_ERROR: return "Syntax error";
409 case ASM_ERR_OUT_OF_MEMORY: return "Out of memory";
410 default: return "Unknown assembly error";
411 }
412}
413
Discussion
No comments yet. Start the discussion. Recorded by @patrick-toulme.