uxn

Varvara Ordinator, written in ANSI C(SDL2)
git clone https://git.eamoncaddigan.net/uxn.git
Log | Files | Refs | README | LICENSE

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 = &macros[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 }