asm.c (11877B)
1#include "asm.h" 2#include "util.h" 3#include "tpu.h" 4 5#include <stdarg.h> 6#include <stdbool.h> 7#include <string.h> 8#include <stdint.h> 9 10#define TEXTALPH "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_" 11#define NUMALPH "0123456789" 12#define NAMEALPH TEXTALPH NUMALPH 13#define WHITESPACE " \t\v\r\n," 14 15enum asm_tok { 16 /* Global */ 17 TOK_STDIN, TOK_STDOUT, TOK_TPU, TOK_END, 18 19 /* Operands (order like OP_*) */ 20 TOK_ACC, TOK_NIL, TOK_LEFT, TOK_RIGHT, TOK_UP, 21 TOK_DOWN, TOK_ANY, TOK_LAST, TOK_LIT, TOK_NAME, 22 23 /* Instructions (order like INST_*) */ 24 TOK_NOP, TOK_MOV, TOK_SWP, TOK_SAV, TOK_ADD, 25 TOK_SUB, TOK_NEG, TOK_XOR, TOK_AND, TOK_JMP, 26 TOK_JEQ, TOK_JNE, TOK_JRO, TOK_SHL, TOK_SHR, 27 28 /* Misc */ 29 TOK_COMMENT, TOK_LABEL, TOK_XPOS, TOK_YPOS, TOK_NL, TOK_EOF 30}; 31 32struct asm_tokenizer { 33 const char *filepath; 34 FILE *file; 35 enum asm_tok tok; 36 char *tokstr; 37 size_t lineno, off; 38 char linebuf[256]; 39}; 40 41static const char *tok_strs[] = { 42 /* Global */ 43 "stdin", "stdout", "tpu", "end", 44 45 /* Operands (order like OP_*) */ 46 "acc", "nil", "left", "right", "up", "down", 47 "any", "last", NULL, NULL, 48 49 /* Instructions (order like INST_*) */ 50 "nop", "mov", "swp", "sav", "add", 51 "sub", "neg", "xor", "and", "jmp", 52 "jeq", "jne", "jro", "shl", "shr", 53 54 /* Misc */ 55 NULL, NULL, NULL, NULL, NULL, NULL 56}; 57 58static const char *tok_reprs[] = { 59 /* Global */ 60 "'STDIN'", "'STDOUT'", "'TPU'", "'END'", 61 62 /* Operands (order like OP_*) */ 63 "'ACC'", "'NIL'", "'LEFT'", "'RIGHT'", "'UP'", "'DOWN'", 64 "'ANY'", "'LAST'", "<LIT>", "<NAME>", 65 66 /* Instructions (order like INST_*) */ 67 "'NOP'", "'MOV'", "'SWP'", "'SAV'", "'ADD'", 68 "'SUB'", "'NEG'", "'XOR'", "'AND'", "'JMP'", 69 "'JEZ'", "'JNZ'", "'JRO'", "'SHL'", "'SHR'", 70 71 /* Misc */ 72 "#<COMMENT>", "<LABEL>:", "X<INT>", "Y<INT>", "<NL>", "<EOF>" 73}; 74 75static bool 76is_lit(const char *str) 77{ 78 unsigned long v; 79 const char *s; 80 char *end; 81 82 if (!strncmp(str, "0b", 2)) { 83 for (v = 0, s = str + 2; *s; s++) 84 v = (2 * v) + (*s == '1'); 85 } else { 86 v = strtoul(str, &end, 0); 87 if (!end || *end) return false; 88 } 89 90 return v < 256; 91} 92 93static uint8_t 94str_to_lit(const char *str) 95{ 96 const char *s; 97 unsigned v; 98 99 if (!strncmp(str, "0b", 2)) { 100 for (v = 0, s = str + 2; *s; s++) 101 v = 2 * v + (*s == '1'); 102 return (uint8_t) v; 103 } else { 104 return (uint8_t) strtoul(str, NULL, 0); 105 } 106} 107 108static enum tpu_port_dir 109tok_to_dir(enum asm_tok tok) 110{ 111 return tok - TOK_LEFT + DIR_LEFT; 112} 113 114static bool 115is_int(const char *str) 116{ 117 char *end; 118 long v; 119 120 v = strtol(str, &end, 10); 121 if (!end || *end) return false; 122 if (v < INT32_MIN || v > INT32_MAX) return false; 123 124 return true; 125} 126 127enum tpu_inst_type 128tok_to_inst(enum asm_tok tok) 129{ 130 if (tok < TOK_NOP || tok > TOK_SHR) abort(); 131 return tok - TOK_NOP + INST_NOP; 132} 133 134enum tpu_inst_op_type 135tok_to_optype(enum asm_tok tok) 136{ 137 if (tok < TOK_ACC || tok > TOK_NAME) abort(); 138 return tok - TOK_ACC + OP_ACC; 139} 140 141struct tpu_inst_op 142tok_to_op(struct asm_tokenizer *tokenizer, enum asm_tok tok) 143{ 144 struct tpu_inst_op op; 145 146 op.type = tok_to_optype(tok); 147 if (op.type == OP_LIT) { 148 op.val.lit = str_to_lit(tokenizer->tokstr); 149 } else if (op.type == OP_LABEL) { 150 op.val.label = strdup(tokenizer->tokstr); 151 if (!op.val.label) die("strdup:"); 152 } 153 154 return op; 155} 156 157static size_t 158strlcat_op_name(char *buf, struct tpu_inst_op *op, size_t n) 159{ 160 char hhbuf[4]; 161 162 if (op->type == OP_LIT) { 163 snprintf(hhbuf, 4, "%hhu", op->val.lit); 164 return strdcat(buf, hhbuf, n); 165 } else if (op->type == OP_LABEL) { 166 return strdcat(buf, op->val.label, n); 167 } else { 168 return strdcat(buf, op_reprs[op->type], n); 169 } 170} 171 172size_t 173asm_print_inst(char *buf, size_t n, struct tpu_inst *inst) 174{ 175 size_t len; 176 177 len = strdcpy(buf, inst_reprs[inst->type], n); 178 if (inst->opcnt >= 1) { 179 len += strdcat(buf, " ", n); 180 len += strlcat_op_name(buf, &inst->ops[0], n); 181 } 182 if (inst->opcnt >= 2) { 183 len += strdcat(buf, ", ", n); 184 len += strlcat_op_name(buf, &inst->ops[1], n); 185 } 186 187 return len; 188} 189 190static enum asm_tok 191tok_next(struct asm_tokenizer *tok) 192{ 193 size_t len; 194 char *s; 195 int i; 196 197 if (!tok->linebuf[tok->off]) { 198 if (feof(tok->file)) return TOK_EOF; 199 s = fgets(tok->linebuf, sizeof(tok->linebuf), tok->file); 200 if (!s && !feof(tok->file)) die("fgets:"); 201 if (!s) return TOK_NL; 202 203 len = strlen(s); 204 if (len && s[len-1] != '\n' && !feof(tok->file)) 205 die("load: line %lu too long", tok->lineno); 206 if (len && s[len-1] == '\n') s[len-1] = '\0'; 207 208 tok->lineno += 1; 209 tok->tokstr = s; 210 tok->off = 0; 211 return TOK_NL; 212 } 213 214 s = tok->linebuf + tok->off; 215 len = strspn(s, WHITESPACE); 216 tok->off += len; 217 if (!s[len]) return TOK_NL; 218 tok->tokstr = (s += len); 219 220 len = strcspn(s, WHITESPACE); 221 tok->off += len; 222 if (s[len]) { 223 s[len] = '\0'; 224 tok->off += 1; 225 } 226 227 for (i = 0; i <= TOK_EOF; i++) { 228 if (tok_strs[i] && !strcasecmp(s, tok_strs[i])) 229 return (enum asm_tok) i; 230 } 231 232 if (is_lit(s)) { 233 return TOK_LIT; 234 } else if (*s == '#') { 235 tok->off += strlen(tok->linebuf + tok->off); 236 return TOK_COMMENT; 237 } else if (len && strchr(TEXTALPH, *s) 238 && strspn(s, NAMEALPH) == len-1 && s[len-1] == ':') { 239 s[len-1] = '\0'; 240 return TOK_LABEL; 241 } else if (*s == 'X' && is_int(s+1)) { 242 return TOK_XPOS; 243 } else if (*s == 'Y' && is_int(s+1)) { 244 return TOK_YPOS; 245 } else if (strchr(TEXTALPH, *s) 246 && strspn(s, NAMEALPH) == strlen(s)) { 247 return TOK_NAME; 248 } else { 249 die("load: line %lu, invalid token '%s'", tok->lineno, s); 250 } 251} 252 253static enum asm_tok 254tok_next_in(struct asm_tokenizer *tokenizer, ...) 255{ 256 va_list ap, cpy; 257 enum asm_tok tok; 258 bool first; 259 int arg; 260 261 tok = tok_next(tokenizer); 262 263 va_copy(cpy, ap); 264 265 va_start(cpy, tokenizer); 266 while ((arg = va_arg(cpy, int)) > 0) { 267 if (tok == arg) return tok; 268 } 269 va_end(cpy); 270 271 fprintf(stderr, "tis-as: load: "); 272 fprintf(stderr, "line %lu, got tok %s, expected one of (", 273 tokenizer->lineno, tok_reprs[tok]); 274 first = true; 275 va_start(ap, tokenizer); 276 while ((arg = va_arg(ap, int)) > 0) { 277 if (!first) fputc(',', stderr); 278 fputs(tok_reprs[arg], stderr); 279 first = false; 280 } 281 va_end(ap); 282 fputs(")\n", stderr); 283 284 exit(1); 285} 286 287static void 288tpu_validate(struct tpu *tpu) 289{ 290 size_t dst; 291 int i; 292 293 for (i = 0; i < tpu->inst_cnt; i++) { 294 if (tpu->insts[i].opcnt >= 1 295 && tpu->insts[i].ops[0].type == OP_LABEL) { 296 dst = tpu_label_get(tpu, tpu->insts[i].ops[0].val.label); 297 if (dst == TPU_MAX_INST) 298 die("load: tpu X%i Y%i, label '%s' not defined", 299 tpu->x, tpu->y, 300 tpu->insts[i].ops[0].val.label); 301 } 302 } 303} 304 305void 306tis_load(struct tis *tis, const char *filepath, FILE *tis_stdin, FILE *tis_stdout) 307{ 308 struct asm_tokenizer tokenizer; 309 struct tpu_inst_op op1, op2; 310 enum tpu_inst_type inst; 311 struct tpu *tpu = NULL; 312 struct tpu_port *port; 313 enum tpu_port_dir stdin_dir, stdout_dir; 314 bool stdin_set, stdout_set; 315 int stdin_x, stdin_y; 316 int stdout_x, stdout_y; 317 enum asm_tok tok, optok; 318 size_t i; 319 320 tis_deinit(tis); 321 tis_init(tis, tis_stdin, tis_stdout); 322 323 stdin_set = stdout_set = false; 324 325 tokenizer.filepath = filepath; 326 tokenizer.file = fopen(filepath, "r"); 327 if (!tokenizer.file) die("load: fopen '%s':", filepath); 328 329 tokenizer.lineno = 0; 330 tokenizer.off = 0; 331 tokenizer.tokstr = NULL; 332 tokenizer.linebuf[tokenizer.off] = '\0'; 333 while ((tok = tok_next(&tokenizer)) != TOK_EOF) { 334 switch (tok) { 335 case TOK_STDIN: 336 if (tpu || stdin_set) goto disallowed; 337 tok_next_in(&tokenizer, TOK_XPOS, -1); 338 stdin_x = atoi(tokenizer.tokstr + 1); 339 tok_next_in(&tokenizer, TOK_YPOS, -1); 340 stdin_y = atoi(tokenizer.tokstr + 1); 341 optok = tok_next_in(&tokenizer, TOK_COMMENT, 342 TOK_LEFT, TOK_RIGHT, TOK_UP, TOK_DOWN, TOK_NL, -1); 343 if (optok != TOK_NL && optok != TOK_COMMENT) { 344 stdin_dir = tok_to_dir(optok); 345 tok_next_in(&tokenizer, TOK_COMMENT, TOK_NL, -1); 346 } else { 347 stdin_dir = DIR_UP; 348 } 349 stdin_set = true; 350 break; 351 case TOK_STDOUT: 352 if (tpu || stdout_set) goto disallowed; 353 tok_next_in(&tokenizer, TOK_XPOS, -1); 354 stdout_x = atoi(tokenizer.tokstr + 1); 355 tok_next_in(&tokenizer, TOK_YPOS, -1); 356 stdout_y = atoi(tokenizer.tokstr + 1); 357 optok = tok_next_in(&tokenizer, TOK_COMMENT, 358 TOK_LEFT, TOK_RIGHT, TOK_UP, TOK_DOWN, TOK_NL, -1); 359 if (optok != TOK_NL && optok != TOK_COMMENT) { 360 stdout_dir = tok_to_dir(optok); 361 tok_next_in(&tokenizer, TOK_COMMENT, TOK_NL, -1); 362 } else { 363 stdout_dir = DIR_DOWN; 364 } 365 stdout_set = true; 366 break; 367 case TOK_TPU: 368 if (tpu) goto disallowed; 369 tpu = tis_add_tpu(tis); 370 tpu_init(tpu); 371 tpu->tis = tis; 372 tok_next_in(&tokenizer, TOK_XPOS, -1); 373 tpu->x = atoi(tokenizer.tokstr + 1); 374 tok_next_in(&tokenizer, TOK_YPOS, -1); 375 tpu->y = atoi(tokenizer.tokstr + 1); 376 tok_next_in(&tokenizer, TOK_COMMENT, TOK_NL, -1); 377 if (!tpu_map_add(&tis->tpu_map, tpu)) 378 die("load: duplicate tpu location X%i Y%i", 379 tpu->x, tpu->y); 380 break; 381 case TOK_END: 382 if (!tpu) goto disallowed; 383 tpu_validate(tpu); 384 tpu = NULL; 385 tok_next_in(&tokenizer, TOK_COMMENT, TOK_NL, -1); 386 break; 387 case TOK_NOP: case TOK_MOV: case TOK_SWP: case TOK_SAV: 388 case TOK_ADD: case TOK_SUB: case TOK_NEG: case TOK_XOR: 389 case TOK_AND: case TOK_JMP: case TOK_JEQ: case TOK_JNE: 390 case TOK_JRO: case TOK_SHL: case TOK_SHR: 391 if (!tpu) goto disallowed; 392 inst = tok_to_inst(tok); 393 394 optok = tok_next_in(&tokenizer, TOK_ACC, 395 TOK_NIL, TOK_LEFT, TOK_RIGHT, TOK_UP, 396 TOK_DOWN, TOK_ANY, TOK_LAST, TOK_LIT, 397 TOK_NAME, TOK_COMMENT, TOK_NL, -1); 398 if (optok == TOK_COMMENT || optok == TOK_NL) { 399 if (!tpu_add_inst(tpu, inst, 0, op1, op2)) 400 die("load: line %lu, invalid instruction", 401 tokenizer.lineno-1); 402 break; 403 } 404 op1 = tok_to_op(&tokenizer, optok); 405 406 optok = tok_next_in(&tokenizer, TOK_ACC, 407 TOK_NIL, TOK_LEFT, TOK_RIGHT, TOK_UP, 408 TOK_DOWN, TOK_ANY, TOK_LAST, TOK_LIT, 409 TOK_NAME, TOK_COMMENT, TOK_NL, -1); 410 if (optok == TOK_COMMENT || optok == TOK_NL) { 411 if (!tpu_add_inst(tpu, inst, 1, op1, op2)) 412 die("load: line %lu, invalid instruction", 413 tokenizer.lineno-1); 414 break; 415 } 416 op2 = tok_to_op(&tokenizer, optok); 417 418 tok_next_in(&tokenizer, TOK_COMMENT, TOK_NL, -1); 419 if (!tpu_add_inst(tpu, inst, 2, op1, op2)) 420 die("load: line %lu, invalid instruction", 421 tokenizer.lineno-1); 422 break; 423 case TOK_COMMENT: 424 tok_next_in(&tokenizer, TOK_NL, -1); 425 break; 426 case TOK_LABEL: 427 if (!tpu_label_add(tpu, tokenizer.tokstr, tpu->inst_cnt)) 428 die("load: line %lu, duplicate label (pos)", 429 tokenizer.lineno); 430 break; 431 case TOK_NL: 432 break; 433 default: 434 goto disallowed; 435 } 436 } 437 438 for (tpu = tis->tpu_vec.tpus, i = 0; i < tis->tpu_vec.cnt; i++, tpu++) 439 tpu_init_ports(tpu, &tis->tpu_map); 440 441 for (tpu = tis->tpu_vec.tpus, i = 0; i < tis->tpu_vec.cnt; i++, tpu++) { 442 tpu_attach_ports(tpu); 443 444 if (stdin_set && tpu->x == stdin_x && tpu->y == stdin_y) { 445 port = &tpu->ports[stdin_dir]; 446 if (port->dst_tpu) die("load: stdin port in use"); 447 port->attached = true; 448 port->dst_tpu = NULL; 449 port->dst_port = &tis->stdin_port; 450 port->type = PORT_IN; 451 tis->stdin_port.attached = true; 452 tis->stdin_port.dst_tpu = tpu; 453 tis->stdin_port.dst_port = port; 454 } 455 456 if (stdout_set && tpu->x == stdout_x && tpu->y == stdout_y) { 457 port = &tpu->ports[stdout_dir]; 458 if (port->dst_tpu) die("load: stdout port in use"); 459 port->attached = true; 460 port->dst_tpu = NULL; 461 port->dst_port = &tis->stdout_port; 462 port->type = PORT_OUT; 463 tis->stdout_port.attached = true; 464 tis->stdout_port.dst_tpu = tpu; 465 tis->stdout_port.dst_port = port; 466 } 467 } 468 469 if (stdin_set && !tis->stdin_port.attached) 470 die("load: stdin tpu (X%i Y%i) not found", 471 stdin_x, stdin_y); 472 473 if (stdout_set && !tis->stdout_port.attached) 474 die("load: stdout tpu (X%i Y%i) not found", 475 stdout_x, stdout_y); 476 477 fclose(tokenizer.file); 478 479 return; 480 481disallowed: 482 if (tok == TOK_NAME) { 483 die("load: line %lu, unexpected token '%s'", 484 tokenizer.lineno, tokenizer.tokstr); 485 } else { 486 die("load: line %lu, token %s not allowed here", 487 tokenizer.lineno, tok_reprs[tok]); 488 } 489}