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}