tis100

Reimplementation of Zachtronics TIS-100 as a TUI game
git clone https://git.sinitax.com/sinitax/tis100
Log | Files | Refs | sfeed.txt

asm.c (14642B)


      1#include "asm.h"
      2#include "util.h"
      3#include "tpu.h"
      4
      5#include <ctype.h>
      6#include <stdarg.h>
      7#include <stdbool.h>
      8#include <stdio.h>
      9#include <string.h>
     10#include <stdint.h>
     11
     12#define TEXTALPH "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_"
     13#define NUMALPH "0123456789"
     14#define PORTALPH "abcdefghijklmnopqrstuvwxyz0123456789"
     15#define NAMEALPH TEXTALPH NUMALPH
     16#define WHITESPACE " \t\v\r\n,"
     17
     18enum asm_tok {
     19	/* Missing */
     20	TOK_NONE = -1,
     21
     22	/* Global */
     23	TOK_IN, TOK_OUT, TOK_TPU, TOK_END,
     24
     25	/* Operands (order like OP_*) */
     26	TOK_ACC, TOK_NIL, TOK_LEFT, TOK_RIGHT, TOK_UP, TOK_DOWN,
     27	TOK_ANY, TOK_LAST, TOK_LIT, TOK_NAME,
     28
     29	/* Instructions (order like INST_*) */
     30	TOK_NOP, TOK_MOV, TOK_SWP, TOK_SAV, TOK_ADD, TOK_SUB,
     31	TOK_NEG, TOK_JMP, TOK_JEZ, TOK_JNZ, TOK_JGZ, TOK_JLZ, TOK_JRO,
     32
     33	/* Misc */
     34	TOK_COMMENT, TOK_LABEL, TOK_XPOS, TOK_YPOS, TOK_NL, TOK_EOF,
     35};
     36
     37struct asm_tokenizer {
     38	const char *filepath;
     39	FILE *file;
     40	enum asm_tok next;
     41	char *tokstr;
     42	size_t lineno, off;
     43	char linebuf[256];
     44};
     45
     46static const char *tok_strs[] = {
     47	/* Global */
     48	NULL, NULL, "tpu", "end",
     49
     50	/* Operands (order like OP_*) */
     51	"acc", "nil", "left", "right", "up", "down",
     52	"any", "last", NULL, NULL,
     53
     54	/* Instructions (order like INST_*) */
     55	"nop", "mov", "swp", "sav", "add", "sub",
     56	"neg", "jmp", "jez", "jnz", "jgz", "jlz", "jro",
     57
     58	/* Misc */
     59	NULL, NULL, NULL, NULL, NULL, NULL
     60};
     61
     62static const char *tok_reprs[] = {
     63	/* Global */
     64	"IN.<C>", "OUT.<C>", "TPU", "END",
     65
     66	/* Operands (order like OP_*) */
     67	"ACC", "NIL", "LEFT", "RIGHT", "UP", "DOWN",
     68	"ANY", "LAST", "<LIT>", "<NAME>",
     69
     70	/* Instructions (order like INST_*) */
     71	"NOP", "MOV", "SWP", "SAV", "ADD", "SUB",
     72	"NEG", "JMP", "JEZ", "JNZ", "JGZ", "JLZ", "JRO",
     73
     74	/* Misc */
     75	"#<COMMENT>", "<LABEL>:", "X<INT>", "Y<INT>", "<NL>", "<EOF>"
     76};
     77
     78static bool
     79is_int(const char *str)
     80{
     81	char *end;
     82	long v;
     83
     84	v = strtol(str, &end, 10);
     85	if (!end || *end) return false;
     86	if (v < INT32_MIN || v > INT32_MAX) return false;
     87
     88	return true;
     89}
     90
     91static int
     92asm_port_char_to_index(char c)
     93{
     94	char *p;
     95
     96	p = strchr(PORTALPH, tolower(c));
     97	if (!p) die("invalid io port char '%c'", c);
     98	return (int) (p - PORTALPH);
     99}
    100
    101static bool
    102asm_is_lit(const char *str)
    103{
    104	const char *c;
    105
    106	c = str;
    107	if (*c == '-') c++;
    108	else if (*c == '+') c++;
    109	for (; *c && isdigit(*c); c++);
    110	return !*c;
    111}
    112
    113static int
    114asm_str_to_lit(const char *str)
    115{
    116	int m, v, b, i, o;
    117
    118	if (*str == '-') {
    119		m = -1;
    120		o = 1;
    121	} else if (*str == '+') {
    122		m = 1;
    123		o = 1;
    124	} else {
    125		m = 1;
    126		o = 0;
    127	}
    128
    129	for (b = 1, i = o; str[i]; i++)
    130		b *= 10;
    131
    132	if (i >= 4 + o) {
    133		v = 1000;
    134	} else {
    135		for (v = 0, i = o; str[i]; i++) {
    136			b /= 10;
    137			v += b * (str[i] - '0');
    138		}
    139	}
    140
    141	return MIN(MAX(m * v, -999), 999);
    142}
    143
    144enum tpu_inst_type
    145tok_to_inst(enum asm_tok tok)
    146{
    147	if (tok < TOK_NOP || tok > TOK_JRO) abort();
    148	return tok - TOK_NOP + INST_NOP;
    149}
    150
    151enum tpu_inst_op_type
    152tok_to_optype(enum asm_tok tok)
    153{
    154	if (tok < TOK_ACC || tok > TOK_NAME) abort();
    155	return tok - TOK_ACC + OP_ACC;
    156}
    157
    158struct tpu_inst_op
    159tok_to_op(struct asm_tokenizer *tokenizer, enum asm_tok tok)
    160{
    161	struct tpu_inst_op op;
    162
    163	op.type = tok_to_optype(tok);
    164	if (op.type == OP_LIT) {
    165		op.val.lit = asm_str_to_lit(tokenizer->tokstr);
    166	} else if (op.type == OP_LABEL) {
    167		op.val.label = strdup(tokenizer->tokstr);
    168		if (!op.val.label) die("strdup:");
    169	}
    170
    171	return op;
    172}
    173
    174static size_t
    175strlcat_op_name(char *buf, struct tpu_inst_op *op, size_t n)
    176{
    177	char tmp[5];
    178
    179	if (op->type == OP_LIT) {
    180		snprintf(tmp, 5, "%i", op->val.lit);
    181		return strdcat(buf, tmp, n);
    182	} else if (op->type == OP_LABEL) {
    183		return strdcat(buf, op->val.label, n);
    184	} else {
    185		return strdcat(buf, op_reprs[op->type], n);
    186	}
    187}
    188
    189size_t
    190asm_print_inst(char *buf, size_t n, struct tpu_inst *inst, size_t max)
    191{
    192	size_t len, op;
    193
    194	len = strdcpy(buf, inst_reprs[inst->type], n);
    195	if (inst->opcnt >= 1) {
    196		len += strdcat(buf, " ", n);
    197		len += strlcat_op_name(buf, &inst->ops[0], n);
    198	}
    199	if (inst->opcnt >= 2) {
    200		op = strdcat(buf, ", ", n);
    201		op += strlcat_op_name(buf, &inst->ops[1], n);
    202		if (len + op > max && len < n) {
    203			buf[len] = '\0';
    204			op = strdcat(buf, ",", n);
    205			op += strlcat_op_name(buf, &inst->ops[1], n);
    206		}
    207		len += op;
    208	}
    209
    210	return len;
    211}
    212
    213static enum asm_tok
    214tok_next(struct asm_tokenizer *tok)
    215{
    216	enum asm_tok v;
    217	size_t len;
    218	char *s;
    219	int i;
    220
    221	if (tok->next != TOK_NONE) {
    222		v = tok->next;
    223		tok->next = TOK_NONE;
    224		return v;
    225	}
    226
    227	if (!tok->linebuf[tok->off]) {
    228		if (feof(tok->file)) return TOK_EOF;
    229		s = fgets(tok->linebuf, sizeof(tok->linebuf), tok->file);
    230		if (!s && !feof(tok->file)) die("fgets:");
    231		if (!s) return TOK_NL;
    232
    233		len = strlen(s);
    234		if (len && s[len-1] != '\n' && !feof(tok->file))
    235			die("load: line %lu too long", tok->lineno);
    236		if (len && s[len-1] == '\n') s[len-1] = '\0';
    237
    238		tok->lineno += 1;
    239		tok->tokstr = s;
    240		tok->off = 0;
    241		return TOK_NL;
    242	}
    243
    244	s = tok->linebuf + tok->off;
    245	len = strspn(s, WHITESPACE);
    246	tok->off += len;
    247	if (!s[len]) return TOK_NL;
    248	tok->tokstr = (s += len);
    249
    250	len = strcspn(s, WHITESPACE);
    251	tok->off += len;
    252	if (s[len]) {
    253		s[len] = '\0';
    254		tok->off += 1;
    255	}
    256
    257	for (i = 0; i <= TOK_EOF; i++) {
    258		if (tok_strs[i] && !strcasecmp(s, tok_strs[i]))
    259			return (enum asm_tok) i;
    260	}
    261
    262	if (!strncasecmp(s, "in.", 3) && len == 4) {
    263		return TOK_IN;
    264	} else if (!strncasecmp(s, "out.", 4) && len == 5) {
    265		return TOK_OUT;
    266	} else if (asm_is_lit(s)) {
    267		return TOK_LIT;
    268	} else if (*s == '#') {
    269		tok->tokstr = tok->linebuf + tok->off;
    270		tok->off += strlen(tok->linebuf + tok->off);
    271		return TOK_COMMENT;
    272	} else if (len && strchr(TEXTALPH, *s)
    273			&& strspn(s, NAMEALPH) == len-1 && s[len-1] == ':') {
    274		s[len-1] = '\0';
    275		return TOK_LABEL;
    276	} else if (*s == 'X' && is_int(s+1)) {
    277		return TOK_XPOS;
    278	} else if (*s == 'Y' && is_int(s+1)) {
    279		return TOK_YPOS;
    280	} else if (strchr(TEXTALPH, *s)
    281			&& strspn(s, NAMEALPH) == strlen(s)) {
    282		return TOK_NAME;
    283	} else {
    284		die("load: line %lu, invalid token '%s'", tok->lineno, s);
    285	}
    286}
    287
    288static enum asm_tok
    289tok_next_in(struct asm_tokenizer *tokenizer, ...)
    290{
    291	va_list ap, cpy;
    292	enum asm_tok tok;
    293	bool first;
    294	int arg;
    295
    296	tok = tok_next(tokenizer);
    297
    298	va_copy(cpy, ap);
    299
    300	va_start(cpy, tokenizer);
    301	while ((arg = va_arg(cpy, int)) > 0) {
    302		if (tok == arg) return tok;
    303	}
    304	va_end(cpy);
    305
    306	fprintf(stderr, "tis-as: load: ");
    307	fprintf(stderr, "line %lu, got tok %s, expected one of (",
    308		tokenizer->lineno, tok_reprs[tok]);
    309	first = true;
    310	va_start(ap, tokenizer);
    311	while ((arg = va_arg(ap, int)) > 0) {
    312		if (!first) fputc(',', stderr);
    313		fputs(tok_reprs[arg], stderr);
    314		first = false;
    315	}
    316	va_end(ap);
    317	fputs(")\n", stderr);
    318
    319	exit(1);
    320}
    321
    322static void
    323tpu_validate(struct tpu *tpu)
    324{
    325	int dst, i;
    326
    327	for (i = 0; i < tpu->inst_cnt; i++) {
    328		if (tpu->insts[i].opcnt >= 1
    329				&& tpu->insts[i].ops[0].type == OP_LABEL) {
    330			dst = label_map_get(&tpu->label_map,
    331				tpu->insts[i].ops[0].val.label);
    332			if (dst < 0)
    333				die("load: tpu X%i Y%i, label '%s' not defined",
    334					tpu->x, tpu->y,
    335					tpu->insts[i].ops[0].val.label);
    336		}
    337	}
    338}
    339
    340void
    341tis_load_asm(struct tis *tis, const char *filepath)
    342{
    343	struct tpu_io_port *io_port;
    344	struct asm_tokenizer tokenizer;
    345	struct tpu_inst_op op1, op2;
    346	enum tpu_inst_type inst_type;
    347	struct tpu_inst *inst;
    348	struct tpu *tpu = NULL;
    349	struct tpu_map_link *link;
    350	struct tpu_port *port;
    351	enum asm_tok tok, optok;
    352	char rowbuf[TPU_MAX_COLS+1];
    353	size_t colsused;
    354	int io_x, io_y;
    355	ssize_t i, k;
    356	size_t len;
    357	char c;
    358
    359	tis->asm_filepath = strdup(filepath);
    360
    361	tokenizer.filepath = filepath;
    362	tokenizer.file = fopen(filepath, "r");
    363	if (!tokenizer.file) die("load: fopen '%s':", filepath);
    364
    365	tokenizer.next = TOK_NONE;
    366	tokenizer.lineno = 0;
    367	tokenizer.off = 0;
    368	tokenizer.tokstr = NULL;
    369	tokenizer.linebuf[tokenizer.off] = '\0';
    370
    371	colsused = 0;
    372	while ((tok = tok_next(&tokenizer)) != TOK_EOF) {
    373		switch (tok) {
    374		case TOK_IN:
    375		case TOK_OUT:
    376			if (tpu) goto disallowed;
    377
    378			io_port = malloc(sizeof(struct tpu_io_port));
    379			if (!io_port) die("malloc:");
    380
    381			len = strlen(tokenizer.tokstr);
    382			c = tokenizer.tokstr[len-1];
    383			k = asm_port_char_to_index(c);
    384
    385			if (tok == TOK_IN) {
    386				if (tis->in_ports[k])
    387					die("load: line %lu, duplicate in.%c",
    388						tokenizer.lineno, c);
    389				tis->in_ports[k] = io_port;
    390			} else {
    391				if (tis->out_ports[k])
    392					die("load: loute %lu, duplicate out.%c",
    393						tokenizer.lineno, c);
    394				tis->out_ports[k] = io_port;
    395			}
    396
    397			tok_next_in(&tokenizer, TOK_XPOS, -1);
    398			io_x = atoi(tokenizer.tokstr + 1);
    399			tok_next_in(&tokenizer, TOK_YPOS, -1);
    400			io_y = atoi(tokenizer.tokstr + 1);
    401			tok_next_in(&tokenizer, TOK_NL, -1);
    402
    403			if (tok == TOK_IN) {
    404				tpu_io_port_init(io_port, c, DIR_UP,
    405					PORT_IN, io_x, io_y);
    406			} else {
    407				tpu_io_port_init(io_port, c, DIR_DOWN,
    408					PORT_OUT, io_x, io_y);
    409			}
    410
    411			colsused = 0;
    412			break;
    413		case TOK_TPU:
    414			if (tpu) goto disallowed;
    415			tpu = malloc(sizeof(struct tpu));
    416			if (!tpu) die("malloc:");
    417			tpu_init(tpu);
    418			tok_next_in(&tokenizer, TOK_XPOS, -1);
    419			tpu->x = atoi(tokenizer.tokstr + 1);
    420			tok_next_in(&tokenizer, TOK_YPOS, -1);
    421			tpu->y = atoi(tokenizer.tokstr + 1);
    422			tok_next_in(&tokenizer, TOK_NL, -1);
    423			if (!tpu_map_add(&tis->tpu_map, tpu))
    424				die("load: duplicate tpu location X%i Y%i",
    425					tpu->x, tpu->y);
    426			colsused = 0;
    427			break;
    428		case TOK_END:
    429			if (!tpu) goto disallowed;
    430			tpu_validate(tpu);
    431			tpu = NULL;
    432			tok_next_in(&tokenizer, TOK_NL, -1);
    433			colsused = 0;
    434			break;
    435		case TOK_NOP: case TOK_MOV: case TOK_SWP: case TOK_SAV:
    436		case TOK_ADD: case TOK_SUB: case TOK_NEG: case TOK_JMP:
    437		case TOK_JEZ: case TOK_JNZ: case TOK_JGZ: case TOK_JLZ:
    438		case TOK_JRO:
    439			if (!tpu) goto disallowed;
    440			inst_type = tok_to_inst(tok);
    441
    442			optok = tok_next_in(&tokenizer, TOK_ACC,
    443				TOK_NIL, TOK_LEFT, TOK_RIGHT, TOK_UP, TOK_DOWN,
    444				TOK_ANY, TOK_LAST, TOK_LIT, TOK_NAME, TOK_NL, -1);
    445			if (optok == TOK_NL) {
    446				inst = tpu_add_inst(tpu, inst_type, 0, op1, op2);
    447				if (!inst) {
    448					die("load: line %lu, invalid instruction",
    449						tokenizer.lineno-1);
    450				}
    451				goto inst_check;
    452			}
    453			op1 = tok_to_op(&tokenizer, optok);
    454
    455			optok = tok_next_in(&tokenizer, TOK_ACC,
    456				TOK_NIL, TOK_LEFT, TOK_RIGHT, TOK_UP, TOK_DOWN,
    457				TOK_ANY, TOK_LAST, TOK_LIT, TOK_NAME, TOK_NL, -1);
    458			if (optok == TOK_NL) {
    459				inst = tpu_add_inst(tpu, inst_type, 1, op1, op2);
    460				if (!inst) {
    461					die("load: line %lu, invalid instruction",
    462						tokenizer.lineno-1);
    463				}
    464				goto inst_check;
    465			}
    466			op2 = tok_to_op(&tokenizer, optok);
    467
    468			tok_next_in(&tokenizer, TOK_NL, -1);
    469
    470			inst = tpu_add_inst(tpu, inst_type, 2, op1, op2);
    471			if (!inst) die("load: line %lu, invalid instruction",
    472				tokenizer.lineno-1);
    473
    474inst_check:
    475			tpu->rows += 1;
    476			if (tpu->inst_cnt > TPU_MAX_INST_CNT || tpu->rows > TPU_MAX_ROWS)
    477				die("load: line %lu, tpu has too many rows",
    478					tokenizer.lineno-1);
    479
    480			len = asm_print_inst(rowbuf, sizeof(rowbuf),
    481				inst, TPU_MAX_COLS - colsused);
    482			if (colsused + len > TPU_MAX_COLS)
    483				die("load: line %lu, tpu row is too long (%zu,%zu)",
    484					tokenizer.lineno-1, colsused, len);
    485
    486			colsused = 0;
    487			break;
    488		case TOK_COMMENT:
    489			if (tpu && !strcmp(tokenizer.tokstr, "DISABLED"))
    490				tpu->disabled = true;
    491			tok_next_in(&tokenizer, TOK_NL, -1);
    492			break;
    493		case TOK_LABEL:
    494			len = strlen(tokenizer.tokstr);
    495			strncpy(rowbuf, tokenizer.tokstr, TPU_MAX_COLS);
    496			optok = tok_next(&tokenizer);
    497			if (!label_map_add(&tpu->label_map, rowbuf,
    498					(int) tpu->inst_cnt, optok != TOK_NL))
    499				die("load: line %lu, duplicate label %s (pos)",
    500					tokenizer.lineno, rowbuf);
    501			if (optok == TOK_NL) {
    502				tpu->rows += 1;
    503				if (tpu->rows > TPU_MAX_ROWS)
    504					die("load: line %lu, line does not fit",
    505						tokenizer.lineno);
    506				colsused = 0;
    507			} else {
    508				tokenizer.next = optok;
    509				colsused = len + 1;
    510			}
    511			if (len + 1 > TPU_MAX_COLS)
    512				die("load: line %lu, label too long",
    513					tokenizer.lineno);
    514			break;
    515		case TOK_NL:
    516			colsused = 0;
    517			break;
    518		default:
    519			goto disallowed;
    520		}
    521	}
    522
    523	for (i = 0; i < TPU_MAP_BUCKETS; i++) {
    524		for (link = tis->tpu_map.buckets[i]; link; link = link->next) {
    525			tpu_init_ports(link->tpu, &tis->tpu_map);
    526
    527			for (k = -TIS_MAX_IO_PORTS; k < TIS_MAX_IO_PORTS; k++) {
    528				io_port = k < 0 ? tis->in_ports[-k-1]
    529					: tis->out_ports[k];
    530				if (!io_port || io_port->port.attached) continue;
    531				if (link->tpu->x != io_port->x) continue;
    532				if (link->tpu->y != io_port->y) continue;
    533				port = &link->tpu->ports[io_port->dir];
    534				if (port->attached) {
    535					die("load: io_port X%i Y%i (%s) busy",
    536						io_port->x, io_port->y,
    537						dir_reprs[io_port->dir]);
    538				}
    539				port->attached = true;
    540				port->dst_port = &io_port->port;
    541				port->type = io_port->type;
    542				io_port->port.attached = true;
    543				io_port->port.dst_port = port;
    544			}
    545		}
    546	}
    547
    548	for (i = -TIS_MAX_IO_PORTS; i < TIS_MAX_IO_PORTS; i++) {
    549		io_port = i < 0 ? tis->in_ports[-i-1] : tis->out_ports[i];
    550		if (io_port && !io_port->port.attached) {
    551			die("load: io_port X%i Y%i (%s) not found",
    552				io_port->x, io_port->y,
    553				dir_reprs[io_port->dir]);
    554		}
    555	}
    556
    557	fclose(tokenizer.file);
    558
    559	return;
    560
    561disallowed:
    562	if (tok == TOK_NAME) {
    563		die("load: line %lu, unexpected token '%s'",
    564			tokenizer.lineno, tokenizer.tokstr);
    565	} else {
    566		die("load: line %lu, token %s not allowed here",
    567			tokenizer.lineno, tok_reprs[tok]);
    568	}
    569}
    570
    571void
    572tis_load(struct tis *tis, const char **argv)
    573{
    574	const char **arg;
    575	int n, i;
    576	char c;
    577
    578	if (!*argv) die("missing argv[0]");
    579
    580	for (arg = argv + 1; *arg; arg++) {
    581		if (!strcmp(*arg, "-h") || !strcmp(*arg, "--help")) {
    582			fprintf(stderr, "Usage: %s [-h] [-s] "
    583				"[--in.X IN].. [--out.X OUT].. ASM\n", argv[0]);
    584		} else if (!strcmp(*arg, "-s") || !strcmp(*arg, "--stats")) {
    585			tis->show_stats = true;
    586		} else if (sscanf(*arg, "--in.%c%n", &c, &n) == 1 && n == 6) {
    587			/* after tis_load_asm.. */
    588			arg++;
    589		} else if (sscanf(*arg, "--out.%c%n", &c, &n) == 1 && n == 7) {
    590			/* after tis_load_asm.. */
    591			arg++;
    592		} else {
    593			if (tis->asm_filepath)
    594				die("multiple asm files");
    595			tis_load_asm(tis, *arg);
    596		}
    597	}
    598
    599	for (arg = argv + 1; *arg; arg++) {
    600		if (sscanf(*arg, "--in.%c%n", &c, &n) == 1 && n == 6) {
    601			i = asm_port_char_to_index(c);
    602			if (!tis->in_ports[i])
    603				die("in.%c io port not initialized", c);
    604			tis->in_ports[i]->file = fopen(*++arg, "r");
    605			if (!tis->in_ports[i]->file)
    606				die("fopen '%s':", *arg);
    607			setvbuf(tis->in_ports[i]->file, NULL, _IONBF, 0);
    608		} else if (sscanf(*arg, "--out.%c%n", &c, &n) == 1 && n == 7) {
    609			i = asm_port_char_to_index(c);
    610			if (!tis->out_ports[i])
    611				die("out.%c io port not initialized", c);
    612			tis->out_ports[i]->file = fopen(*++arg, "w+");
    613			if (!tis->out_ports[i]->file)
    614				die("fopen '%s':", *arg);
    615			setvbuf(tis->out_ports[i]->file, NULL, _IONBF, 0);
    616		}
    617	}
    618}