diff options
Diffstat (limited to 'asm.c')
| -rw-r--r-- | asm.c | 433 |
1 files changed, 433 insertions, 0 deletions
@@ -0,0 +1,433 @@ +#include "asm.h" +#include "util.h" +#include "tpu.h" + +#include <stdarg.h> +#include <string.h> +#include <stdint.h> + +#define NAMEALPH "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_" +#define WHITESPACE " \t\v\r\n" + +enum asm_tok { + /* Global */ + TOK_STDIN, TOK_STDOUT, TOK_TPU, TOK_END, + + /* Operands (order like OP_*) */ + TOK_ACC, TOK_BAK, TOK_NIL, TOK_LEFT, TOK_RIGHT, + TOK_UP, TOK_DOWN, TOK_ANY, TOK_LAST, TOK_LIT, TOK_NAME, + + /* Instructions (order like INST_*) */ + TOK_NOP, TOK_MOV, TOK_SWP, TOK_SAV, TOK_ADD, TOK_SUB, + TOK_NEG, TOK_JMP, TOK_JEZ, TOK_JNZ, TOK_JGZ, TOK_JLZ, TOK_JRO, + + /* Misc */ + TOK_COMMENT, TOK_LABEL, TOK_XPOS, TOK_YPOS, TOK_NL, TOK_EOF +}; + +struct asm_tokenizer { + const char *filepath; + FILE *file; + enum asm_tok tok; + char *tokstr; + size_t lineno, off; + char linebuf[256]; +}; + +static const char *tok_reprs[] = { + /* Global */ + "'STDIN'", "'STDOUT'", "'TPU'", "'END'", + + /* Operands (order like OP_*) */ + "'ACC'", "'BAK'", "'NIL'", "'LEFT'", "'RIGHT'", + "'UP'", "'DOWN'", "'ANY'", "'LAST'", "<LIT>", "<NAME>", + + /* Instructions (order like INST_*) */ + "'NOP'", "'MOV'", "'SWP'", "'SAV'", "'ADD'", "'SUB'", + "'NEG'", "'JMP'", "'JEZ'", "'JNZ'", "'JGZ'", "'JLZ'", "'JRO'", + + /* Misc */ + "#<COMMENT>", "<LABEL>:", "X<INT>", "Y<INT>", "<NL>", "<EOF>" +}; + +static bool +is_lit(const char *str) +{ + unsigned long v; + char *end; + + v = strtoul(str, &end, 10); + if (!end || *end) return false; + + return v < 256; +} + +static uint8_t +str_to_lit(const char *str) +{ + return (uint8_t) atoi(str); +} + +enum tpu_inst_type +tok_to_inst(enum asm_tok tok) +{ + if (tok < TOK_NOP || tok > TOK_JRO) abort(); + return tok - TOK_NOP + INST_NOP; +} + +enum tpu_inst_op_type +tok_to_optype(enum asm_tok tok) +{ + if (tok < TOK_ACC || tok > TOK_NAME) abort(); + return tok - TOK_ACC + OP_ACC; +} + +static size_t +strlcat_op_name(char *buf, struct tpu_inst_op *op, size_t n) +{ + size_t len; + + if (op->type == OP_LIT) { + len = strlen(buf); + snprintf(buf + len, n - len, "%hhu", op->lit); + return op->lit; + } else if (op->type == OP_LABEL) { + return strdcat(buf, op->label, n); + } else { + return strdcat(buf, op_reprs[op->type], n); + } +} + +size_t +asm_print_inst(char *buf, size_t n, struct tpu_inst *inst) +{ + size_t len; + + len = strdcpy(buf, inst_reprs[inst->type], n); + if (inst->opcnt >= 1) { + len += strdcat(buf, " ", n); + len += strlcat_op_name(buf, &inst->ops[0], n); + } + if (inst->opcnt >= 2) { + len += strdcat(buf, ", ", n); + len += strlcat_op_name(buf, &inst->ops[1], n); + } + + return len; +} + +static enum asm_tok +tok_next(struct asm_tokenizer *tok) +{ + size_t len; + char *s; + + if (!tok->linebuf[tok->off]) { + if (feof(tok->file)) return TOK_EOF; + s = fgets(tok->linebuf, sizeof(tok->linebuf), tok->file); + if (!s && !feof(tok->file)) die("fgets:"); + if (!s) return TOK_NL; + + len = strlen(s); + if (len && s[len-1] != '\n' && !feof(tok->file)) + die("tis_load: line %lu too long", tok->lineno); + if (len && s[len-1] == '\n') s[len-1] = '\0'; + + tok->lineno += 1; + tok->tokstr = s; + tok->off = 0; + return TOK_NL; + } + + s = tok->linebuf + tok->off; + len = strspn(s, WHITESPACE); + tok->off += len; + s += len; + tok->tokstr = s; + if (!*s) return TOK_NL; + + len = strcspn(s, WHITESPACE ","); + tok->off += len; + if (s[len]) { + s[len] = '\0'; + tok->off += 1; + } + + if (!strcasecmp(s, "stdin")) { + return TOK_STDIN; + } else if (!strcasecmp(s, "stdout")) { + return TOK_STDOUT; + } else if (!strcasecmp(s, "tpu")) { + return TOK_TPU; + } else if (!strcasecmp(s, "end")) { + return TOK_END; + } else if (!strcasecmp(s, "acc")) { + return TOK_ACC; + } else if (!strcasecmp(s, "bak")) { + return TOK_BAK; + } else if (!strcasecmp(s, "nil")) { + return TOK_NIL; + } else if (!strcasecmp(s, "left")) { + return TOK_LEFT; + } else if (!strcasecmp(s, "right")) { + return TOK_RIGHT; + } else if (!strcasecmp(s, "up")) { + return TOK_UP; + } else if (!strcasecmp(s, "down")) { + return TOK_DOWN; + } else if (!strcasecmp(s, "any")) { + return TOK_ANY; + } else if (!strcasecmp(s, "last")) { + return TOK_LAST; + } else if (is_lit(s)) { + return TOK_LIT; + } else if (!strcasecmp(s, "nop")) { + return TOK_NOP; + } else if (!strcasecmp(s, "mov")) { + return TOK_MOV; + } else if (!strcasecmp(s, "swp")) { + return TOK_SWP; + } else if (!strcasecmp(s, "sav")) { + return TOK_SAV; + } else if (!strcasecmp(s, "add")) { + return TOK_ADD; + } else if (!strcasecmp(s, "sub")) { + return TOK_SUB; + } else if (!strcasecmp(s, "neg")) { + return TOK_NEG; + } else if (!strcasecmp(s, "jmp")) { + return TOK_JMP; + } else if (!strcasecmp(s, "jez")) { + return TOK_JEZ; + } else if (!strcasecmp(s, "jnz")) { + return TOK_JNZ; + } else if (!strcasecmp(s, "jgz")) { + return TOK_JGZ; + } else if (!strcasecmp(s, "jlz")) { + return TOK_JLZ; + } else if (!strcasecmp(s, "jro")) { + return TOK_JRO; + } else if (*s == '#') { + tok->off += strlen(tok->linebuf + tok->off); + return TOK_COMMENT; + } else if (len && strspn(s, NAMEALPH) == len-1 && s[len-1] == ':') { + return TOK_LABEL; + } else if (*s == 'X' && atoi(s+1) > 0) { + return TOK_XPOS; + } else if (*s == 'Y' && atoi(s+1) > 0) { + return TOK_YPOS; + } else if (strspn(s, NAMEALPH) == strlen(s)) { + return TOK_NAME; + } else { + die("tis_load: line %lu, invalid token '%s'", tok->lineno, s); + } +} + +static enum asm_tok +tok_next_in(struct asm_tokenizer *tokenizer, ...) +{ + va_list ap, cpy; + enum asm_tok tok; + bool first; + int arg; + + tok = tok_next(tokenizer); + + va_copy(cpy, ap); + + va_start(cpy, tokenizer); + while ((arg = va_arg(cpy, int)) > 0) { + if (tok == arg) return tok; + } + va_end(cpy); + + fprintf(stderr, "tis-as: load: "); + fprintf(stderr, "line %lu, got tok %s, expected one of (", + tokenizer->lineno, tok_reprs[tok]); + first = true; + va_start(ap, tokenizer); + while ((arg = va_arg(ap, int)) > 0) { + if (!first) fputc(',', stderr); + fputs(tok_reprs[arg], stderr); + first = false; + } + va_end(ap); + fputs(")\n", stderr); + + exit(1); +} + +void +tis_load(struct tis *tis, const char *filepath) +{ + struct asm_tokenizer tokenizer; + enum tpu_inst_op_type op1, op2; + enum asm_tok op1_tok, op2_tok; + uint8_t op1_lit, op2_lit; + enum tpu_inst_type inst; + struct tpu *tpu = NULL; + struct tpu_map_link *link; + struct tpu_port *port; + int stdin_x, stdin_y; + int stdout_x, stdout_y; + enum asm_tok tok; + char *label; + size_t i; + + stdin_x = stdin_y = 0; + stdout_x = stdout_y = 0; + + tokenizer.filepath = filepath; + tokenizer.file = fopen(filepath, "r"); + if (!tokenizer.file) die("tis_load: fopen '%s':", filepath); + + tokenizer.lineno = 0; + tokenizer.off = 0; + tokenizer.tokstr = NULL; + tokenizer.linebuf[tokenizer.off] = '\0'; + while ((tok = tok_next(&tokenizer)) != TOK_EOF) { + switch (tok) { + case TOK_STDIN: + if (tpu) 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); + break; + case TOK_STDOUT: + if (tpu) 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); + break; + case TOK_TPU: + if (tpu) goto disallowed; + tpu = malloc(sizeof(struct tpu)); + if (!tpu) die("malloc:"); + tpu_init(tpu); + tok_next_in(&tokenizer, TOK_XPOS, -1); + tpu->x = (size_t) atoi(tokenizer.tokstr + 1); + tok_next_in(&tokenizer, TOK_YPOS, -1); + tpu->y = (size_t) atoi(tokenizer.tokstr + 1); + tok_next_in(&tokenizer, TOK_NL, -1); + tpu_map_add(&tis->tpu_map, tpu); + break; + case TOK_END: + if (!tpu) goto disallowed; + tpu = NULL; + tok_next_in(&tokenizer, TOK_NL, -1); + break; + case TOK_NOP: case TOK_MOV: case TOK_SWP: case TOK_SAV: + case TOK_ADD: case TOK_SUB: case TOK_NEG: case TOK_JMP: + case TOK_JEZ: case TOK_JNZ: case TOK_JGZ: case TOK_JLZ: + case TOK_JRO: + if (!tpu) goto disallowed; + inst = tok_to_inst(tok); + + op1_tok = 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) { + tpu_add_inst(tpu, inst, -1, 0, -1, 0); + break; + } + + op1 = tok_to_optype(op1_tok); + if (op1 == OP_LIT) + op1_lit = str_to_lit(tokenizer.tokstr); + + op2_tok = 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) { + tpu_add_inst(tpu, inst, (int) op1, + op1_lit, -1, 0); + break; + } + + op2 = tok_to_optype(op2_tok); + if (op2 == OP_LIT) + op2_lit = str_to_lit(tokenizer.tokstr); + tok_next_in(&tokenizer, TOK_NL, -1); + tpu_add_inst(tpu, inst, (int) op1, op1_lit, + (int) op2, op2_lit); + break; + case TOK_COMMENT: + tok_next_in(&tokenizer, TOK_NL, -1); + break; + case TOK_LABEL: + label = strdup(tokenizer.tokstr); + if (!label_map_add(&tpu->labels, label, tpu->inst_cnt)) + die("tis_load: line %lu, duplicate label", + tokenizer.lineno); + tok_next_in(&tokenizer, TOK_NL, -1); + break; + case TOK_NL: + break; + default: + goto disallowed; + } + } + + if (stdin_x == 0 || stdin_y == 0) + die("tis_load: stdin tpu not set"); + + if (stdout_x == 0 || stdout_y == 0) + die("tis_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) { + port = &link->tpu->ports[DIR_UP]; + if (port->attached) + die("tis_load: stdin port in use"); + port->attached = true; + port->dst_tpu = NULL; + port->dst_port = &tis->stdin_port; + port->type = PORT_IN; + tis->stdin_port.attached = true; + tis->stdin_port.dst_tpu = link->tpu; + tis->stdin_port.dst_port = port; + } + + if (link->x == stdout_x && link->y == stdout_y) { + port = &link->tpu->ports[DIR_DOWN]; + if (port->attached) + die("tis_load: stdout port in use"); + port->attached = true; + port->dst_tpu = NULL; + port->dst_port = &tis->stdout_port; + port->type = PORT_OUT; + tis->stdout_port.attached = true; + tis->stdout_port.dst_tpu = link->tpu; + tis->stdout_port.dst_port = port; + } + } + } + + if (!tis->stdin_port.attached) + die("tis_load: stdin tpu (X%i Y%i) not found", + stdin_x, stdin_y); + + if (!tis->stdout_port.attached) + die("tis_load: stdout tpu (X%i Y%i) not found", + stdout_x, stdout_y); + + fclose(tokenizer.file); + + return; + +disallowed: + if (tok == TOK_NAME) { + die("tis_load: line %lu, unexpected token '%s'", + tokenizer.lineno, tokenizer.tokstr); + } else { + die("tis_load: line %lu, token %s not allowed here", + tokenizer.lineno, tok_reprs[tok]); + } +} |
