summaryrefslogtreecommitdiffstats
path: root/asm.c
diff options
context:
space:
mode:
Diffstat (limited to 'asm.c')
-rw-r--r--asm.c433
1 files changed, 433 insertions, 0 deletions
diff --git a/asm.c b/asm.c
new file mode 100644
index 0000000..f08a515
--- /dev/null
+++ b/asm.c
@@ -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]);
+ }
+}