uxnasm.c (11547B)
1 #include <stdio.h> 2 3 /* 4 Copyright (c) 2021-2024 Devine Lu Linvega, Andrew Alderwick 5 6 Permission to use, copy, modify, and distribute this software for any 7 purpose with or without fee is hereby granted, provided that the above 8 copyright notice and this permission notice appear in all copies. 9 10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 WITH REGARD TO THIS SOFTWARE. 12 */ 13 14 /* clang-format off */ 15 16 #define PAGE 0x0100 17 #define ishex(x) (shex(x) >= 0) 18 #define isopc(x) (findopcode(x) || scmp(x, "BRK", 4)) 19 #define isinvalid(x) (!x[0] || ishex(x) || isopc(x) || find(runes, x[0]) >= 0) 20 #define writeshort(x) (writebyte(x >> 8, ctx) && writebyte(x & 0xff, ctx)) 21 #define findlabel(x) finditem(x, labels, labels_len) 22 #define findmacro(x) finditem(x, macros, macro_len) 23 #define error_top(id, msg) !printf("%s: %s\n", id, msg) 24 #define error_asm(id) !printf("%s: %s in @%s, %s:%d.\n", id, token, scope, ctx->path, ctx->line) 25 #define error_ref(id) !printf("%s: %s, %s:%d\n", id, r->name, r->data, r->line) 26 27 typedef unsigned char Uint8; 28 typedef signed char Sint8; 29 typedef unsigned short Uint16; 30 typedef struct { int line; char *path; } Context; 31 typedef struct { char *name, rune, *data; Uint16 addr, refs, line; } Item; 32 33 static int ptr, length; 34 static char token[0x30], scope[0x40], lambda[0x05]; 35 static char dict[0x8000], *dictnext = dict; 36 static Uint8 data[0x10000], lambda_stack[0x100], lambda_ptr, lambda_len; 37 static Uint16 labels_len, refs_len, macro_len; 38 static Item labels[0x400], refs[0x1000], macros[0x100]; 39 40 static char *runes = "|$@&,_.-;=!?#\"%~"; 41 static char *hexad = "0123456789abcdef"; 42 static char ops[][4] = { 43 "LIT", "INC", "POP", "NIP", "SWP", "ROT", "DUP", "OVR", 44 "EQU", "NEQ", "GTH", "LTH", "JMP", "JCN", "JSR", "STH", 45 "LDZ", "STZ", "LDR", "STR", "LDA", "STA", "DEI", "DEO", 46 "ADD", "SUB", "MUL", "DIV", "AND", "ORA", "EOR", "SFT" 47 }; 48 49 /* clang-format on */ 50 51 static int 52 find(char *s, char t) 53 { 54 int i = 0; 55 char c; 56 while((c = *s++)) { 57 if(c == t) return i; 58 i++; 59 } 60 return -1; 61 } 62 63 static int 64 shex(char *s) 65 { 66 int d, n = 0; 67 char c; 68 while((c = *s++)) { 69 d = find(hexad, c); 70 if(d < 0) return d; 71 n = n << 4, n |= d; 72 } 73 return n; 74 } 75 76 static int 77 scmp(char *a, char *b, int len) 78 { 79 int i = 0; 80 while(a[i] == b[i]) 81 if(!a[i] || ++i >= len) return 1; 82 return 0; 83 } 84 85 static char * 86 copy(char *src, char *dst, char c) 87 { 88 while(*src && *src != c) *dst++ = *src++; 89 *dst++ = 0; 90 return dst; 91 } 92 93 static char * 94 save(char *s, char c) 95 { 96 char *o = dictnext; 97 while((*dictnext++ = *s++) && *s); 98 *dictnext++ = c; 99 return o; 100 } 101 102 static char * 103 join(char *a, char j, char *b) 104 { 105 char *res = dictnext; 106 save(a, j), save(b, 0); 107 return res; 108 } 109 110 static char * 111 push(char *s, char c) 112 { 113 char *d; 114 for(d = dict; d < dictnext; d++) { 115 char *ss = s, *dd = d, a, b; 116 while((a = *dd++) == (b = *ss++)) 117 if(!a && !b) return d; 118 } 119 return save(s, c); 120 } 121 122 static Item * 123 finditem(char *name, Item *list, int len) 124 { 125 int i; 126 if(name[0] == '&') 127 name = join(scope, '/', name + 1); 128 for(i = 0; i < len; i++) 129 if(scmp(list[i].name, name, 0x40)) 130 return &list[i]; 131 return NULL; 132 } 133 134 static Uint8 135 findopcode(char *s) 136 { 137 int i; 138 for(i = 0; i < 0x20; i++) { 139 int m = 3; 140 if(!scmp(ops[i], s, 3)) continue; 141 if(!i) i |= (1 << 7); 142 while(s[m]) { 143 if(s[m] == '2') 144 i |= (1 << 5); 145 else if(s[m] == 'r') 146 i |= (1 << 6); 147 else if(s[m] == 'k') 148 i |= (1 << 7); 149 else 150 return 0; 151 m++; 152 } 153 return i; 154 } 155 return 0; 156 } 157 158 static int 159 walkcomment(FILE *f, Context *ctx) 160 { 161 char c, last = 0; 162 int depth = 1; 163 while(f && fread(&c, 1, 1, f)) { 164 if(c <= 0x20) { 165 if(c == 0xa) ctx->line++; 166 if(last == '(') 167 depth++; 168 else if(last == ')' && --depth < 1) 169 return 1; 170 last = 0; 171 } else if(last <= 0x20) 172 last = c; 173 else 174 last = '~'; 175 } 176 return error_asm("Comment incomplete"); 177 } 178 179 static int parse(char *w, FILE *f, Context *ctx); 180 181 static int 182 walkmacro(Item *m, Context *ctx) 183 { 184 unsigned char c; 185 char *dataptr = m->data, *_token = token; 186 while((c = *dataptr++)) { 187 if(c < 0x21) { 188 *_token = 0x00; 189 if(token[0] && !parse(token, NULL, ctx)) return 0; 190 _token = token; 191 } else if(_token - token < 0x2f) 192 *_token++ = c; 193 else 194 return error_asm("Token size exceeded"); 195 } 196 return 1; 197 } 198 199 static int 200 walkfile(FILE *f, Context *ctx) 201 { 202 unsigned char c; 203 char *_token = token; 204 while(f && fread(&c, 1, 1, f)) { 205 if(c < 0x21) { 206 *_token = 0x00; 207 if(token[0] && !parse(token, f, ctx)) return 0; 208 if(c == 0xa) ctx->line++; 209 _token = token; 210 } else if(_token - token < 0x2f) 211 *_token++ = c; 212 else 213 return error_asm("Token size exceeded"); 214 } 215 *_token = 0; 216 return parse(token, f, ctx); 217 } 218 219 static char * 220 makelambda(int id) 221 { 222 lambda[0] = (char)0xce; 223 lambda[1] = (char)0xbb; 224 lambda[2] = hexad[id >> 0x4]; 225 lambda[3] = hexad[id & 0xf]; 226 return lambda; 227 } 228 229 static int 230 makemacro(char *name, FILE *f, Context *ctx) 231 { 232 int depth = 0; 233 char c; 234 Item *m; 235 if(macro_len >= 0x100) return error_asm("Macros limit exceeded"); 236 if(isinvalid(name)) return error_asm("Macro invalid"); 237 if(findmacro(name)) return error_asm("Macro duplicate"); 238 if(findlabel(name)) return error_asm("Label duplicate"); 239 m = ¯os[macro_len++]; 240 m->name = push(name, 0); 241 m->data = dictnext; 242 while(f && fread(&c, 1, 1, f) && c != '{') 243 if(c == 0xa) ctx->line++; 244 while(f && fread(&c, 1, 1, f)) { 245 if(c == 0xa) ctx->line++; 246 if(c == '%') return error_asm("Macro nested"); 247 if(c == '{') depth++; 248 if(c == '}' && --depth) break; 249 *dictnext++ = c; 250 } 251 *dictnext++ = 0; 252 return 1; 253 } 254 255 static int 256 makelabel(char *name, int setscope, Context *ctx) 257 { 258 Item *l; 259 if(name[0] == '&') 260 name = join(scope, '/', name + 1); 261 if(labels_len >= 0x400) return error_asm("Labels limit exceeded"); 262 if(isinvalid(name)) return error_asm("Label invalid"); 263 if(findmacro(name)) return error_asm("Label duplicate"); 264 if(findlabel(name)) return error_asm("Label duplicate"); 265 l = &labels[labels_len++]; 266 l->name = push(name, 0); 267 l->addr = ptr; 268 l->refs = 0; 269 if(setscope) copy(name, scope, '/'); 270 return 1; 271 } 272 273 static int 274 makeref(char *label, char rune, Uint16 addr, Context *ctx) 275 { 276 Item *r; 277 if(refs_len >= 0x1000) return error_asm("References limit exceeded"); 278 r = &refs[refs_len++]; 279 if(label[0] == '{') { 280 lambda_stack[lambda_ptr++] = lambda_len; 281 r->name = push(makelambda(lambda_len++), 0); 282 if(label[1]) return error_asm("Label invalid"); 283 } else if(label[0] == '&' || label[0] == '/') { 284 r->name = join(scope, '/', label + 1); 285 } else 286 r->name = push(label, 0); 287 r->rune = rune; 288 r->addr = addr; 289 r->line = ctx->line; 290 r->data = ctx->path; 291 return 1; 292 } 293 294 static int 295 writepad(char *w, Context *ctx) 296 { 297 Item *l; 298 int rel = w[0] == '$' ? ptr : 0; 299 if(ishex(w + 1)) { 300 ptr = shex(w + 1) + rel; 301 return 1; 302 } 303 if((l = findlabel(w + 1))) { 304 ptr = l->addr + rel; 305 return 1; 306 } 307 return error_asm("Padding invalid"); 308 } 309 310 static int 311 writebyte(Uint8 b, Context *ctx) 312 { 313 if(ptr < PAGE) 314 return error_asm("Writing zero-page"); 315 else if(ptr >= 0x10000) 316 return error_asm("Writing outside memory"); 317 else if(ptr < length) 318 return error_asm("Writing rewind"); 319 data[ptr++] = b; 320 if(b) 321 length = ptr; 322 return 1; 323 } 324 325 static int 326 writehex(char *w, Context *ctx) 327 { 328 if(*w == '#') 329 writebyte(findopcode("LIT") | !!(++w)[2] << 5, ctx); 330 if(ishex(w)) { 331 if(w[1] && !w[2]) 332 return writebyte(shex(w), ctx); 333 else if(w[3] && !w[4]) 334 return writeshort(shex(w)); 335 } 336 return error_asm("Hexadecimal invalid"); 337 } 338 339 static int 340 writestring(char *w, Context *ctx) 341 { 342 char c; 343 while((c = *(w++))) 344 if(!writebyte(c, ctx)) return error_asm("String invalid"); 345 return 1; 346 } 347 348 static int 349 assemble(char *filename) 350 { 351 FILE *f; 352 int res; 353 Context ctx; 354 ctx.line = 1; 355 ctx.path = push(filename, 0); 356 if(!(f = fopen(filename, "r"))) 357 return error_top("File missing", filename); 358 res = walkfile(f, &ctx); 359 fclose(f); 360 return res; 361 } 362 363 static int 364 parse(char *w, FILE *f, Context *ctx) 365 { 366 Item *m; 367 switch(w[0]) { 368 case 0x0: return 1; 369 case '(': 370 if(w[1] <= 0x20) 371 return walkcomment(f, ctx); 372 else 373 return error_asm("Invalid word"); 374 case '%': return makemacro(w + 1, f, ctx); 375 case '@': return makelabel(w + 1, 1, ctx); 376 case '&': return makelabel(w, 0, ctx); 377 case '}': return makelabel(makelambda(lambda_stack[--lambda_ptr]), 0, ctx); 378 case '#': return writehex(w, ctx); 379 case '_': return makeref(w + 1, w[0], ptr, ctx) && writebyte(0xff, ctx); 380 case ',': return makeref(w + 1, w[0], ptr + 1, ctx) && writebyte(findopcode("LIT"), ctx) && writebyte(0xff, ctx); 381 case '-': return makeref(w + 1, w[0], ptr, ctx) && writebyte(0xff, ctx); 382 case '.': return makeref(w + 1, w[0], ptr + 1, ctx) && writebyte(findopcode("LIT"), ctx) && writebyte(0xff, ctx); 383 case ':': printf("Deprecated rune %s, use =%s\n", w, w + 1); /* fall-through */ 384 case '=': return makeref(w + 1, w[0], ptr, ctx) && writeshort(0xffff); 385 case ';': return makeref(w + 1, w[0], ptr + 1, ctx) && writebyte(findopcode("LIT2"), ctx) && writeshort(0xffff); 386 case '?': return makeref(w + 1, w[0], ptr + 1, ctx) && writebyte(0x20, ctx) && writeshort(0xffff); 387 case '!': return makeref(w + 1, w[0], ptr + 1, ctx) && writebyte(0x40, ctx) && writeshort(0xffff); 388 case '"': return writestring(w + 1, ctx); 389 case '~': return !assemble(w + 1) ? error_asm("Include missing") : 1; 390 case '$': 391 case '|': return writepad(w, ctx); 392 case '[': 393 case ']': return 1; 394 } 395 if(ishex(w)) return writehex(w, ctx); 396 if(isopc(w)) return writebyte(findopcode(w), ctx); 397 if((m = findmacro(w))) return walkmacro(m, ctx); 398 return makeref(w, ' ', ptr + 1, ctx) && writebyte(0x60, ctx) && writeshort(0xffff); 399 } 400 401 static int 402 resolve(char *filename) 403 { 404 int i, rel; 405 if(!length) return error_top("Output empty", filename); 406 for(i = 0; i < refs_len; i++) { 407 Item *r = &refs[i], *l = findlabel(r->name); 408 Uint8 *rom = data + r->addr; 409 if(!l) return error_ref("Label unknown"); 410 switch(r->rune) { 411 case '_': 412 case ',': 413 *rom = rel = l->addr - r->addr - 2; 414 if((Sint8)data[r->addr] != rel) 415 return error_ref("Reference too far"); 416 break; 417 case '-': 418 case '.': 419 *rom = l->addr; 420 break; 421 case ':': 422 case '=': 423 case ';': 424 *rom++ = l->addr >> 8, *rom = l->addr; 425 break; 426 case '?': 427 case '!': 428 default: 429 rel = l->addr - r->addr - 2; 430 *rom++ = rel >> 8, *rom = rel; 431 break; 432 } 433 l->refs++; 434 } 435 return 1; 436 } 437 438 static int 439 build(char *rompath) 440 { 441 int i; 442 FILE *dst, *dstsym; 443 char *sympath = join(rompath, '.', "sym"); 444 /* rom */ 445 if(!(dst = fopen(rompath, "wb"))) 446 return !error_top("Output file invalid", rompath); 447 for(i = 0; i < labels_len; i++) 448 if(!labels[i].refs && (unsigned char)(labels[i].name[0] - 'A') > 25) 449 printf("-- Unused label: %s\n", labels[i].name); 450 fwrite(data + PAGE, length - PAGE, 1, dst); 451 printf( 452 "Assembled %s in %d bytes(%.2f%% used), %d labels, %d macros.\n", 453 rompath, 454 length - PAGE, 455 (length - PAGE) / 652.80, 456 labels_len, 457 macro_len); 458 /* sym */ 459 if(!(dstsym = fopen(sympath, "w"))) 460 return !error_top("Symbols file invalid", sympath); 461 for(i = 0; i < labels_len; i++) { 462 Uint8 hb = labels[i].addr >> 8, lb = labels[i].addr; 463 char c, d = 0, *name = labels[i].name; 464 fwrite(&hb, 1, 1, dstsym); 465 fwrite(&lb, 1, 1, dstsym); 466 while((c = *name++)) fwrite(&c, 1, 1, dstsym); 467 fwrite(&d, 1, 1, dstsym); 468 } 469 fclose(dst), fclose(dstsym); 470 return 1; 471 } 472 473 int 474 main(int argc, char *argv[]) 475 { 476 ptr = PAGE; 477 copy("on-reset", scope, 0); 478 if(argc == 2 && scmp(argv[1], "-v", 2)) return !printf("Uxnasm - Uxntal Assembler, 15 Jan 2025.\n"); 479 if(argc != 3) return error_top("usage", "uxnasm [-v] input.tal output.rom"); 480 return !assemble(argv[1]) || !resolve(argv[2]) || !build(argv[2]); 481 }