tis100

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

commit 7d73b738a5703d5263a84dcbe3564e2267af6804
parent 41760436d528552d64122bb0c837f4d8274a0bdd
Author: Louis Burda <quent.burda@gmail.com>
Date:   Tue, 25 Jul 2023 05:56:08 +0200

Improve parsing, make stdin / stdout port optional

Diffstat:
Masm.c | 110+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Atest/and.asm | 27+++++++++++++++++++++++++++
Atest/xor.asm | 26++++++++++++++++++++++++++
Mtis-as.c | 7++++---
Mtis-curses.c | 48++++++++++++++++++++++++++++++++++++------------
Mtpu.c | 48++++++++++++++++++++----------------------------
Mtpu.h | 14++++++--------
7 files changed, 179 insertions(+), 101 deletions(-)

diff --git a/asm.c b/asm.c @@ -3,6 +3,7 @@ #include "tpu.h" #include <stdarg.h> +#include <stdbool.h> #include <string.h> #include <stdint.h> @@ -80,6 +81,19 @@ is_lit(const char *str) return v < 256; } +static bool +is_int(const char *str) +{ + char *end; + long v; + + v = strtol(str, &end, 10); + if (!end || *end) return false; + if (v < INT32_MIN || v > INT32_MAX) return false; + + return true; +} + static uint8_t str_to_lit(const char *str) { @@ -100,6 +114,22 @@ tok_to_optype(enum asm_tok tok) return tok - TOK_ACC + OP_ACC; } +struct tpu_inst_op +tok_to_op(struct asm_tokenizer *tokenizer, enum asm_tok tok) +{ + struct tpu_inst_op op; + + op.type = tok_to_optype(tok); + if (op.type == OP_LIT) { + op.val.lit = str_to_lit(tokenizer->tokstr); + } else if (op.type == OP_LABEL) { + op.val.label = strdup(tokenizer->tokstr); + if (!op.val.label) die("strdup:"); + } + + return op; +} + static size_t strlcat_op_name(char *buf, struct tpu_inst_op *op, size_t n) { @@ -184,9 +214,9 @@ tok_next(struct asm_tokenizer *tok) && strspn(s, NAMEALPH) == len-1 && s[len-1] == ':') { s[len-1] = '\0'; return TOK_LABEL; - } else if (*s == 'X' && atoi(s+1) > 0) { + } else if (*s == 'X' && is_int(s+1)) { return TOK_XPOS; - } else if (*s == 'Y' && atoi(s+1) > 0) { + } else if (*s == 'Y' && is_int(s+1)) { return TOK_YPOS; } else if (strchr(TEXTALPH, *s) && strspn(s, NAMEALPH) == strlen(s)) { @@ -242,7 +272,7 @@ tpu_validate(struct tpu *tpu) dst = label_map_get(&tpu->label_map, tpu->insts[i].ops[0].val.label); if (dst == TPU_MAX_INST) - die("load: tpu X%lu Y%lu, label '%s' not defined", + die("load: tpu X%i Y%i, label '%s' not defined", tpu->x, tpu->y, tpu->insts[i].ops[0].val.label); } @@ -253,23 +283,21 @@ void tis_load(struct tis *tis, const char *filepath) { struct asm_tokenizer tokenizer; - int op1, op2; - enum asm_tok op1_tok, op2_tok; - union tpu_inst_op_val op1v, op2v; + struct tpu_inst_op op1, op2; enum tpu_inst_type inst; struct tpu *tpu = NULL; struct tpu_map_link *link; struct tpu_port *port; + bool stdin_set, stdout_set; int stdin_x, stdin_y; int stdout_x, stdout_y; - enum asm_tok tok; + enum asm_tok tok, optok; size_t i; tis_deinit(tis); tis_init(tis); - stdin_x = stdin_y = 0; - stdout_x = stdout_y = 0; + stdin_set = stdout_set = false; tokenizer.filepath = filepath; tokenizer.file = fopen(filepath, "r"); @@ -282,20 +310,22 @@ tis_load(struct tis *tis, const char *filepath) while ((tok = tok_next(&tokenizer)) != TOK_EOF) { switch (tok) { case TOK_STDIN: - if (tpu) goto disallowed; + if (tpu || stdin_set) goto disallowed; tok_next_in(&tokenizer, TOK_XPOS, -1); stdin_x = atoi(tokenizer.tokstr + 1); tok_next_in(&tokenizer, TOK_YPOS, -1); stdin_y = atoi(tokenizer.tokstr + 1); tok_next_in(&tokenizer, TOK_NL, -1); + stdin_set = true; break; case TOK_STDOUT: - if (tpu) goto disallowed; + if (tpu || stdout_set) goto disallowed; tok_next_in(&tokenizer, TOK_XPOS, -1); stdout_x = atoi(tokenizer.tokstr + 1); tok_next_in(&tokenizer, TOK_YPOS, -1); stdout_y = atoi(tokenizer.tokstr + 1); tok_next_in(&tokenizer, TOK_NL, -1); + stdout_set = true; break; case TOK_TPU: if (tpu) goto disallowed; @@ -303,9 +333,9 @@ tis_load(struct tis *tis, const char *filepath) if (!tpu) die("malloc:"); tpu_init(tpu); tok_next_in(&tokenizer, TOK_XPOS, -1); - tpu->x = (size_t) atoi(tokenizer.tokstr + 1); + tpu->x = atoi(tokenizer.tokstr + 1); tok_next_in(&tokenizer, TOK_YPOS, -1); - tpu->y = (size_t) atoi(tokenizer.tokstr + 1); + tpu->y = atoi(tokenizer.tokstr + 1); tok_next_in(&tokenizer, TOK_NL, -1); tpu_map_add(&tis->tpu_map, tpu); break; @@ -322,46 +352,32 @@ tis_load(struct tis *tis, const char *filepath) if (!tpu) goto disallowed; inst = tok_to_inst(tok); - op1_tok = tok_next_in(&tokenizer, TOK_ACC, TOK_BAK, + optok = tok_next_in(&tokenizer, TOK_ACC, TOK_BAK, TOK_NIL, TOK_LEFT, TOK_RIGHT, TOK_UP, TOK_DOWN, TOK_ANY, TOK_LAST, TOK_LIT, TOK_NAME, TOK_NL, -1); - if (op1_tok == TOK_NL) { - if (!tpu_add_inst(tpu, inst, -1, op1v, -1, op2v)) + if (optok == TOK_NL) { + if (!tpu_add_inst(tpu, inst, 0, op1, op2)) die("load: line %lu, invalid instruction", - tokenizer.lineno); + tokenizer.lineno-1); break; } + op1 = tok_to_op(&tokenizer, optok); - op1 = (int) tok_to_optype(op1_tok); - if (op1 == OP_LIT) - op1v.lit = str_to_lit(tokenizer.tokstr); - else if (op1 == OP_LABEL) { - op1v.label = strdup(tokenizer.tokstr); - if (!op1v.label) die("strdup:"); - } - - op2_tok = tok_next_in(&tokenizer, TOK_ACC, TOK_BAK, + optok = tok_next_in(&tokenizer, TOK_ACC, TOK_BAK, TOK_NIL, TOK_LEFT, TOK_RIGHT, TOK_UP, TOK_DOWN, TOK_ANY, TOK_LAST, TOK_LIT, TOK_NAME, TOK_NL, -1); - if (op2_tok == TOK_NL) { - if (!tpu_add_inst(tpu, inst, op1, op1v, -1, op2v)) + if (optok == TOK_NL) { + if (!tpu_add_inst(tpu, inst, 1, op1, op2)) die("load: line %lu, invalid instruction", - tokenizer.lineno); + tokenizer.lineno-1); break; } + op2 = tok_to_op(&tokenizer, optok); - op2 = (int) tok_to_optype(op2_tok); - if (op2 == OP_LIT) - op2v.lit = str_to_lit(tokenizer.tokstr); - else if (op2 == OP_LABEL) { - op2v.label = strdup(tokenizer.tokstr); - if (!op2v.label) die("strdup:"); - } - - if (!tpu_add_inst(tpu, inst, op1, op1v, op2, op2v)) - die("load: line %lu, invalid instruction", - tokenizer.lineno); tok_next_in(&tokenizer, TOK_NL, -1); + if (!tpu_add_inst(tpu, inst, 2, op1, op2)) + die("load: line %lu, invalid instruction", + tokenizer.lineno-1); break; case TOK_COMMENT: tok_next_in(&tokenizer, TOK_NL, -1); @@ -379,17 +395,11 @@ tis_load(struct tis *tis, const char *filepath) } } - if (stdin_x == 0 || stdin_y == 0) - die("load: stdin tpu not set"); - - if (stdout_x == 0 || stdout_y == 0) - die("load: stdout tpu not set"); - for (i = 0; i < TPU_MAP_BUCKETS; i++) { for (link = tis->tpu_map.buckets[i]; link; link = link->next) { tpu_init_ports(link->tpu, &tis->tpu_map); - if (link->x == stdin_x && link->y == stdin_y) { + if (stdin_set && link->x == stdin_x && link->y == stdin_y) { port = &link->tpu->ports[DIR_UP]; if (port->attached) die("load: stdin port in use"); @@ -402,7 +412,7 @@ tis_load(struct tis *tis, const char *filepath) tis->stdin_port.dst_port = port; } - if (link->x == stdout_x && link->y == stdout_y) { + if (stdout_set && link->x == stdout_x && link->y == stdout_y) { port = &link->tpu->ports[DIR_DOWN]; if (port->attached) die("load: stdout port in use"); @@ -417,11 +427,11 @@ tis_load(struct tis *tis, const char *filepath) } } - if (!tis->stdin_port.attached) + if (stdin_set && !tis->stdin_port.attached) die("load: stdin tpu (X%i Y%i) not found", stdin_x, stdin_y); - if (!tis->stdout_port.attached) + if (stdout_set && !tis->stdout_port.attached) die("load: stdout tpu (X%i Y%i) not found", stdout_x, stdout_y); diff --git a/test/and.asm b/test/and.asm @@ -0,0 +1,27 @@ +stdout X1 Y1 + +tpu X1 Y0 + mov 0, DOWN + mov 1, DOWN + mov 0, DOWN + mov 1, DOWN + MOV UP, NIL +end + +tpu X0 Y1 + mov 0, RIGHT + mov 0, RIGHT + mov 1, RIGHT + mov 1, RIGHT + MOV UP, NIL +end + +tpu X1 Y1 + mov 2, ACC + sub UP + sub LEFT + jez H + mov 0, DOWN + jro 2 +H: mov 1, DOWN +end diff --git a/test/xor.asm b/test/xor.asm @@ -0,0 +1,26 @@ +stdout X1 Y1 + +tpu X1 Y0 + mov 0, DOWN + mov 1, DOWN + mov 0, DOWN + mov 1, DOWN + MOV UP, NIL +end + +tpu X0 Y1 + mov 0, RIGHT + mov 0, RIGHT + mov 1, RIGHT + mov 1, RIGHT + MOV UP, NIL +end + +tpu X1 Y1 + MOV UP, ACC + sub LEFT + jnz H + mov 0, DOWN + jro 2 +H: mov 1, DOWN +end diff --git a/tis-as.c b/tis-as.c @@ -50,13 +50,14 @@ main(int argc, const char **argv) tis.stdin_port.out = -1; idle = false; - while (!idle || !prev_idle || tis.stdin_port.reading && !feof(tis_stdin)) { - if (tis.stdin_port.out < 0) { + while (!idle || !prev_idle || tis.stdin_port.attached + && tis.stdin_port.reading && !feof(tis_stdin)) { + if (tis.stdin_port.attached && tis.stdin_port.out < 0) { c = getc(tis_stdin); if (c >= 0) tis.stdin_port.out = c; } - if (tis.stdout_port.in >= 0) { + if (tis.stdout_port.attached && tis.stdout_port.in >= 0) { putc(tis.stdout_port.in, tis_stdout); tis.stdout_port.in = -1; } diff --git a/tis-curses.c b/tis-curses.c @@ -353,6 +353,7 @@ tui_seek(struct tpu *tpu, int dx, int dy) if (maxy == -1 || y > maxy) maxy = y; } } + if (minx == -1 || miny == -1) return; } if (dx == MIN) scrx = minx - 2; @@ -388,12 +389,12 @@ handlekey(int key) scrx += 4; break; case 's': - if (tis.stdin_port.out < 0) { + if (tis.stdin_port.attached && tis.stdin_port.out < 0) { c = getc(tis_stdin); if (c >= 0) tis.stdin_port.out = c; } - if (tis.stdout_port.in >= 0) { + if (tis.stdout_port.attached && tis.stdout_port.in >= 0) { putc(tis.stdout_port.in, tis_stdout); tis.stdout_port.in = -1; } @@ -411,7 +412,7 @@ handlekey(int key) case KEY_LEFT: case KEY_RIGHT: dir = key_to_dir(key); - if (tpu_sel->ports[dir].dst_tpu) + if (tpu_sel && tpu_sel->ports[dir].dst_tpu) tpu_sel = tpu_sel->ports[dir].dst_tpu; tui_seek(tpu_sel, MID, MID); break; @@ -419,13 +420,27 @@ handlekey(int key) } } -void -reset(int ifd, int argc, char **argv) +static struct tpu * +first_tpu(void) +{ + size_t i; + + for (i = 0; i < TPU_MAP_BUCKETS; i++) { + if (tis.tpu_map.buckets[i]) + return tis.tpu_map.buckets[i]->tpu; + } + + return NULL; +} + +static void +reset(int ifd, int argc, char **argv, bool watch) { tis_load(&tis, argv[1]); - if (inotify_add_watch(ifd, argv[1], IN_MODIFY) < 0) - die("inotify_add_watch '%s':", argv[1]); + if (watch) + if (inotify_add_watch(ifd, argv[1], IN_MODIFY) < 0) + die("inotify_add_watch '%s':", argv[1]); if (tis_stdin) fclose(tis_stdin); tis_stdin = fopen(argv[2], "r"); @@ -435,7 +450,11 @@ reset(int ifd, int argc, char **argv) tis_stdout = fopen(argv[3], "w+"); if (!tis_stdout) die("fopen '%s':", argv[3]); - tpu_sel = tis.stdin_port.dst_tpu; + if (tis.stdin_port.attached) { + tpu_sel = tis.stdin_port.dst_tpu; + } else { + tpu_sel = first_tpu(); + } tui_seek(NULL, MID, MID); } @@ -471,7 +490,7 @@ main(int argc, char **argv) ifd = inotify_init1(IN_NONBLOCK); - reset(ifd, argc, argv); + reset(ifd, argc, argv, true); quit = false; while (!quit) { @@ -479,7 +498,7 @@ main(int argc, char **argv) if (len < 0 && errno != EAGAIN) die("inotify_read:"); if (len >= 0) { - reset(ifd, argc, argv); + reset(ifd, argc, argv, true); show_reloaded = 1000 / TIMEOUT; } @@ -496,10 +515,15 @@ main(int argc, char **argv) tui_seek(NULL, MID, MID); break; case 'i': - tui_seek(tis.stdin_port.dst_tpu, MID, MID); + if (tis.stdin_port.attached) + tui_seek(tis.stdin_port.dst_tpu, MID, MID); break; case 'o': - tui_seek(tis.stdout_port.dst_tpu, MID, MID); + if (tis.stdout_port.attached) + tui_seek(tis.stdout_port.dst_tpu, MID, MID); + break; + case 'r': + reset(ifd, argc, argv, false); break; case KEY_CTRL('c'): quit = true; diff --git a/tpu.c b/tpu.c @@ -199,8 +199,7 @@ tpu_init_ports(struct tpu *tpu, struct tpu_map *map) { struct tpu *neighbor; enum tpu_port_dir odir; - size_t x, y; - int i; + int x, y, i; for (i = 0; i < 4; i++) { switch (i) { @@ -262,36 +261,30 @@ tpu_update_ports(struct tpu *tpu) bool tpu_set_inst(struct tpu *tpu, uint8_t pc, enum tpu_inst_type inst_type, - int op1, union tpu_inst_op_val v1, - int op2, union tpu_inst_op_val v2) + unsigned opcnt, struct tpu_inst_op op1, struct tpu_inst_op op2) { struct tpu_inst *inst; inst = &tpu->insts[pc]; inst->type = inst_type; - - if (op2 >= 0) { - inst->ops[1].type = (enum tpu_inst_op_type) op2; - inst->ops[1].val = v1; - inst->ops[0].type = (enum tpu_inst_op_type) op1; - inst->ops[0].val = v2; - inst->opcnt = 2; - } else if (op1 >= 0) { - inst->ops[0].type = (enum tpu_inst_op_type) op1; - inst->ops[0].val = v1; - inst->opcnt = 1; - } else { - inst->opcnt = 0; - } + inst->opcnt = opcnt; + inst->ops[0] = op1; + inst->ops[1] = op2; switch (inst->type) { case INST_NOP: case INST_SAV: case INST_SWP: case INST_NEG: - return inst->opcnt == 0; + if (inst->opcnt != 0) return false; + break; case INST_ADD: case INST_SUB: - return inst->opcnt == 1; + if (inst->opcnt != 1) return false; + break; + case INST_JRO: + if (inst->opcnt != 1) return false; + if (inst->ops[0].type != OP_LIT) return false; + break; case INST_JMP: case INST_JEZ: case INST_JNZ: - case INST_JGZ: case INST_JLZ: case INST_JRO: + case INST_JGZ: case INST_JLZ: if (inst->opcnt != 1) return false; if (inst->ops[0].type != OP_LABEL) return false; break; @@ -308,14 +301,13 @@ tpu_set_inst(struct tpu *tpu, uint8_t pc, enum tpu_inst_type inst_type, bool tpu_add_inst(struct tpu *tpu, enum tpu_inst_type inst_type, - int op1, union tpu_inst_op_val v1, - int op2, union tpu_inst_op_val v2) + unsigned opcnt, struct tpu_inst_op op1, struct tpu_inst_op op2) { if (tpu->inst_cnt >= TPU_MAX_INST) - die("tpu_add_inst: tpu X%lu Y%lu, >= max %i instructions", + die("tpu_add_inst: tpu X%i Y%i, >= max %i instructions", tpu->x, tpu->y, TPU_MAX_INST); return tpu_set_inst(tpu, (uint8_t) tpu->inst_cnt++, - inst_type, op1, v1, op2, v2); + inst_type, opcnt, op1, op2); } /* tpu can always write to an empty port (->out), but only @@ -575,12 +567,12 @@ tpu_map_deinit(struct tpu_map *map) } static struct tpu_map_link ** -tpu_map_link_pos(struct tpu_map *map, size_t x, size_t y) +tpu_map_link_pos(struct tpu_map *map, int x, int y) { struct tpu_map_link **link; size_t i; - i = (x + y) % TPU_MAP_BUCKETS; + i = (size_t) (x + y) % TPU_MAP_BUCKETS; link = &map->buckets[i]; while (*link && !((*link)->x == x && (*link)->y == y)) link = &(*link)->next; @@ -603,7 +595,7 @@ tpu_map_add(struct tpu_map *map, struct tpu *tpu) } struct tpu * -tpu_map_get(struct tpu_map *map, size_t x, size_t y) +tpu_map_get(struct tpu_map *map, int x, int y) { struct tpu_map_link **link; diff --git a/tpu.h b/tpu.h @@ -58,7 +58,7 @@ struct tpu_inst_op { struct tpu_inst { enum tpu_inst_type type; struct tpu_inst_op ops[2]; - uint8_t opcnt; + unsigned opcnt; }; struct tpu_port { @@ -73,7 +73,7 @@ struct tpu_port { struct tpu { enum tpu_status status; - size_t x, y; + int x, y; struct tpu_port ports[4]; int io_port; @@ -90,7 +90,7 @@ struct tpu { }; struct tpu_map_link { - size_t x, y; + int x, y; struct tpu *tpu; struct tpu_map_link *next; }; @@ -119,11 +119,9 @@ struct tpu_inst *tpu_current_inst(struct tpu *tpu); void tpu_init_ports(struct tpu *tpu, struct tpu_map *map); void tpu_update_ports(struct tpu *tpu); bool tpu_set_inst(struct tpu *tpu, uint8_t pc, enum tpu_inst_type inst, - int op1, union tpu_inst_op_val v1, - int op2, union tpu_inst_op_val v2); + unsigned opcnt, struct tpu_inst_op op1, struct tpu_inst_op op2); bool tpu_add_inst(struct tpu *tpu, enum tpu_inst_type inst, - int op1, union tpu_inst_op_val v1, - int op2, union tpu_inst_op_val v2); + unsigned opcnt, struct tpu_inst_op op1, struct tpu_inst_op op2); void tpu_clear_ports(struct tpu *tpu); enum tpu_status tpu_exec_mov(struct tpu *tpu, struct tpu_inst *inst); enum tpu_status tpu_exec(struct tpu *tpu, struct tpu_inst *inst); @@ -132,7 +130,7 @@ enum tpu_status tpu_step(struct tpu *tpu); void tpu_map_init(struct tpu_map *map); void tpu_map_deinit(struct tpu_map *map); void tpu_map_add(struct tpu_map *map, struct tpu *tpu); -struct tpu *tpu_map_get(struct tpu_map *map, size_t x, size_t y); +struct tpu *tpu_map_get(struct tpu_map *map, int x, int y); void tis_init(struct tis *tis); void tis_deinit(struct tis *tis);