tisc.c (18251B)
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <stdint.h> 4 #include <string.h> 5 6 #define VERSION_STRING "v2.3" 7 #define TOT_INSTRUCTIONS 33 8 #define MAX_SYMBOLS 1000 9 #define MAX_SYMBOL_LEN 100 10 #define MAX_LINE_LEN 100 11 #define MAX_PGR_SIZE 0xFFFF 12 #define MAX_LNI_SIZE 17 13 14 #define NR 0x0 15 #define NR_STRING "NIL" 16 #define GR_A 0x1 17 #define GR_A_STRING "GRA" 18 #define GR_B 0x2 19 #define GR_B_STRING "GRB" 20 #define GR_C 0x3 21 #define GR_C_STRING "GRC" 22 23 #define TOKENIZER " \t\n" 24 25 int validSymbols = 0; 26 int addresses[MAX_SYMBOLS]; 27 char symbols[MAX_SYMBOLS][MAX_SYMBOL_LEN]; 28 29 typedef struct InstructionDefintion 30 { 31 char* instructionLabel; 32 int arguements; 33 int instructionLength; 34 uint8_t opcode_mask; 35 int (*assemble)(struct InstructionDefintion*, char* arg[3], uint8_t*, int); 36 } InstructionDefinition_t; 37 38 /* Checks if instruction is valid 39 * 40 * return -1 -> too few arguements 41 * return 0 -> instruction is valid 42 * return 1 -> too many arguements 43 */ 44 int validins(InstructionDefinition_t *ins, const char *opcode, char *arg[]) 45 { 46 int status = 0; 47 48 int supp_args; 49 50 for (supp_args = 0; supp_args < 3; supp_args++) 51 { 52 if (arg[supp_args] == NULL || arg[supp_args][0] == '#') 53 { 54 break; 55 } 56 } 57 58 if (ins->arguements > supp_args) 59 { 60 status = -1; // Too few args, fail 61 } 62 else 63 if (ins->arguements < supp_args) 64 { 65 status = 1; // Too many args, fail 66 } 67 68 return status; 69 } 70 71 void add_label(const char* label, int address) 72 { 73 //add label to table 'o' label 74 strcpy(symbols[validSymbols], label); 75 addresses[validSymbols] = address; 76 validSymbols++; 77 } 78 79 int longest_label() 80 { 81 int i, labelLen = 0; 82 83 for (i = 0; i < validSymbols; i++) 84 { 85 int len = strlen(symbols[i]); 86 if (len > labelLen) 87 { 88 labelLen = len; 89 } 90 } 91 92 return (labelLen > 5) ? labelLen : 5; 93 } 94 95 int label_address(const char *label) 96 { 97 int i, address = -1; 98 99 for (i = 0; i < validSymbols; i++) 100 { 101 if (strcmp(symbols[i], label) == 0) 102 { 103 address = addresses[i]; 104 break; 105 } 106 } 107 108 return address; 109 } 110 111 void update_label(const char* label, int address) 112 { 113 int i; 114 115 for (i = 0; i < validSymbols; i++) 116 { 117 if (strcmp(symbols[i], label) == 0) 118 { 119 addresses[i] = address & 0xFF; 120 break; 121 } 122 } 123 } 124 125 int labelexists(const char *label) 126 { 127 int i, status = 0; 128 129 for (i = 0; i < validSymbols; i++) 130 { 131 if (strcmp(symbols[i], label) == 0) 132 { 133 status = 1; 134 } 135 } 136 137 return status; 138 } 139 140 int process_label_initial(char *label, int address) 141 { 142 int status = 0; 143 144 if (label != NULL) 145 { 146 //duplicate label check 147 if (labelexists(label) == 0) 148 { 149 add_label(label, address); 150 151 status = 1; 152 } 153 } 154 else 155 { 156 status = 1; 157 } 158 159 return status; 160 } 161 162 int process_label_final(char *label, int address) 163 { 164 int status = 0; 165 166 if (label != NULL) 167 { 168 //make sure the label exists 169 if (labelexists(label) == 1) 170 { 171 update_label(label, address); 172 173 status = 1; 174 } 175 } 176 else 177 { 178 status = 1; 179 } 180 181 return status; 182 } 183 184 uint8_t getRegisterEnumeration(char* string) 185 { 186 if (string == NULL) { 187 return 0xFF; 188 } 189 if (strncmp(string, GR_A_STRING, strlen(GR_A_STRING)) == 0) 190 { 191 return GR_A; 192 } 193 if (strncmp(string, GR_B_STRING, strlen(GR_B_STRING)) == 0) 194 { 195 return GR_B; 196 } 197 if (strncmp(string, GR_C_STRING, strlen(GR_C_STRING)) == 0) 198 { 199 return GR_C; 200 } 201 if (strncmp(string, NR_STRING, strlen(NR_STRING)) == 0) 202 { 203 return NR; 204 } 205 206 return 0xFF; 207 } 208 209 int stringToInteger(char* str) 210 { 211 int return_value = -1; 212 213 if (strncmp("0x", str, 2) == 0) 214 { 215 return_value = (int)strtol(str+2, NULL, 16); 216 } 217 else if (strncmp("0b", str, 2) == 0) 218 { 219 return_value = (int)strtol(str+2, NULL, 2); 220 } 221 else 222 { 223 return_value = atoi(str); 224 } 225 226 return return_value; 227 } 228 229 int assemble_0arg( 230 InstructionDefinition_t *definition, 231 char* arg[3], uint8_t* write_buffer, 232 int address) 233 { 234 write_buffer[0] = definition->opcode_mask; 235 236 return 1; 237 } 238 239 int assemble_1arg( 240 InstructionDefinition_t *definition, 241 char* arg[3], uint8_t* write_buffer, 242 int address) 243 { 244 uint8_t argB = getRegisterEnumeration(arg[0]); 245 246 write_buffer[0] = definition->opcode_mask | (argB << 4); 247 248 return 1; 249 } 250 251 int assemble_2arg( 252 InstructionDefinition_t *definition, 253 char* arg[3], uint8_t* write_buffer, 254 int address) 255 { 256 uint8_t argA, argB; 257 258 argA = getRegisterEnumeration(arg[0]); 259 argB = getRegisterEnumeration(arg[1]); 260 261 write_buffer[0] = definition->opcode_mask; 262 263 write_buffer[0] |= ( argA << 2 ) | ( argB << 4); 264 265 return 1; 266 } 267 268 int assemble_cin( 269 InstructionDefinition_t *definition, 270 char* arg[3], uint8_t* write_buffer, 271 int address) 272 { 273 uint8_t argB, argC; 274 275 argB = getRegisterEnumeration(arg[0]); 276 argC = getRegisterEnumeration(arg[1]); 277 278 write_buffer[0] = definition->opcode_mask; 279 280 write_buffer[0] |= ( argB << 4) | ( argC << 6); 281 282 return 1; 283 } 284 285 int assemble_mov( 286 InstructionDefinition_t *definition, 287 char* arg[3], uint8_t* write_buffer, 288 int address) 289 { 290 uint8_t argA, argC; 291 292 argA = getRegisterEnumeration(arg[0]); 293 argC = getRegisterEnumeration(arg[1]); 294 295 write_buffer[0] = definition->opcode_mask; 296 297 write_buffer[0] |= ( argA << 2) | ( argC << 6); 298 299 return 1; 300 } 301 302 int assemble_3arg( 303 InstructionDefinition_t *definition, 304 char* arg[3], uint8_t* write_buffer, 305 int address) 306 { 307 uint8_t argA, argB, argC; 308 309 argA = getRegisterEnumeration(arg[0]); 310 argB = getRegisterEnumeration(arg[1]); 311 argC = getRegisterEnumeration(arg[2]); 312 313 write_buffer[0] = definition->opcode_mask; 314 315 write_buffer[0] |= ( argA << 2 ) | ( argB << 4) | ( argC << 6); 316 317 return 1; 318 } 319 320 int assemble_lli( 321 InstructionDefinition_t *definition, 322 char* arg[3], uint8_t* write_buffer, 323 int address) 324 { 325 int return_value = 0; 326 327 int immediate_value = stringToInteger(arg[0]); 328 329 if ((immediate_value & 0xF0) == 0) 330 { 331 write_buffer[0] = definition->opcode_mask | immediate_value << 2; 332 333 return_value = 1; 334 } 335 else 336 { 337 printf("FATAL: Value provided is invalid: (%i)", immediate_value); 338 printf("FATAL: Value must not be > 15"); 339 } 340 341 return return_value; 342 } 343 344 int assemble_li( 345 InstructionDefinition_t *definition, 346 char* arg[3], uint8_t* write_buffer, 347 int address) 348 { 349 int return_value = 0; 350 351 int immediate_value = stringToInteger(arg[0]); 352 353 if (immediate_value < 256 && immediate_value >= 0) 354 { 355 write_buffer[0] = definition->opcode_mask; 356 write_buffer[1] = immediate_value; 357 358 return_value = 1; 359 } 360 else 361 { 362 printf("FATAL: value provided is invalid %i\n", immediate_value); 363 printf("FATAL: value must be < 256 && >= 0\n"); 364 } 365 366 return return_value; 367 } 368 369 int assemble_lni( 370 InstructionDefinition_t *definition, 371 char* arg[3], uint8_t* write_buffer, 372 int address) 373 { 374 int return_value = 0; 375 376 int byte_len = 0; 377 int bytes[MAX_LNI_SIZE]; 378 if (arg[0][0] == '"') 379 { 380 for (int i = 1; arg[0][i] != '\0'; i++) 381 { 382 383 } 384 } 385 else 386 { 387 char* arg_copy = (char*)malloc(sizeof(char) * strlen(arg[0])); 388 strcpy(arg_copy, arg[0]); 389 char* byte_str = strtok(arg_copy, ","); 390 391 while (byte_str != NULL) 392 { 393 int value = stringToInteger(byte_str); 394 395 if (value > 255 || value < 0) 396 { 397 byte_len = 0; 398 printf("FATAL: value provided is invalid %i\n", value); 399 break; 400 } 401 402 byte_str = strtok(NULL, ","); 403 if (byte_len < MAX_LNI_SIZE) 404 { 405 bytes[byte_len] = value; 406 } 407 byte_len++; 408 } 409 410 free(arg_copy); 411 } 412 413 if (byte_len <= 1) 414 { 415 printf("FATAL: must provide byte array length > 1\n"); 416 } 417 418 if (byte_len > 1 && byte_len < MAX_LNI_SIZE) 419 { 420 definition->instructionLength = byte_len + 1; 421 422 write_buffer[0] = definition->opcode_mask | (byte_len - 1) << 2; 423 424 for (int i = 1; (i-1) < byte_len; i++) 425 { 426 write_buffer[i] = bytes[i - 1]; 427 } 428 429 return_value = 1; 430 } 431 else 432 { 433 printf("FATAL: byte array has too many elements. length must be <= %i\n", MAX_LNI_SIZE); 434 printf("FATAL: length = %i\n", byte_len); 435 } 436 437 return return_value; 438 } 439 440 int assemble_jmp( 441 InstructionDefinition_t *definition, 442 char* arg[3], uint8_t* write_buffer, 443 int address) 444 { 445 int return_value = 0; 446 447 int jump_address = label_address(arg[0]); 448 449 if (jump_address != -1) 450 { 451 write_buffer[0] = definition->opcode_mask; 452 write_buffer[1] = jump_address; 453 454 return_value = 1; 455 } 456 else 457 { 458 printf("Could not find label %s\n", arg[0]); 459 } 460 461 return return_value; 462 } 463 464 int segment( 465 InstructionDefinition_t *definition, 466 char* arg[3], uint8_t* write_buffer, 467 int address) 468 { 469 int return_value = 0; 470 int target_address = stringToInteger(arg[0]); 471 472 if (target_address < MAX_PGR_SIZE && (target_address > address)) 473 { 474 definition->instructionLength = target_address - address; 475 return_value = 1; 476 } 477 else 478 { 479 printf("Segment address must be less than %i\n", MAX_PGR_SIZE); 480 } 481 482 return return_value; 483 } 484 485 int getlabel( 486 InstructionDefinition_t *definition, 487 char* arg[3], uint8_t* write_buffer, 488 int address) 489 { 490 int return_value = 0; 491 492 int label = label_address(arg[0]); 493 494 if (label != -1) 495 { 496 write_buffer[0] = definition->opcode_mask; 497 write_buffer[1] = label; 498 499 return_value = 1; 500 } 501 else 502 { 503 printf("Could not find label %s\n", arg[0]); 504 } 505 506 return return_value; 507 } 508 509 InstructionDefinition_t definitions[TOT_INSTRUCTIONS] = 510 { 511 //opcode,args,size,byte,func 512 { "getlabel", 1, 2, 0x43, getlabel }, 513 { "segment", 1, 0, 0x00, segment }, 514 { "nop", 0, 1, 0x00, assemble_0arg }, 515 { "push", 0, 1, 0x93, assemble_0arg }, 516 { "pop", 0, 1, 0xA3, assemble_0arg }, 517 { "peek", 0, 1, 0xB3, assemble_0arg }, 518 { "lbs", 0, 1, 0x87, assemble_0arg }, 519 { "sbs", 0, 1, 0x8B, assemble_0arg }, 520 { "sps", 0, 1, 0x8F, assemble_0arg }, 521 { "sop_add", 0, 1, 0x00, assemble_0arg }, 522 { "sop_sub", 0, 1, 0x10, assemble_0arg }, 523 { "sop_and", 0, 1, 0x20, assemble_0arg }, 524 { "sop_xor", 0, 1, 0x30, assemble_0arg }, 525 { "sop_xnor", 0, 1, 0x40, assemble_0arg }, 526 { "sop_cin", 0, 1, 0x50, assemble_0arg }, 527 { "sop_lsh", 0, 1, 0x60, assemble_0arg }, 528 { "sop_rsh", 0, 1, 0x70, assemble_0arg }, 529 { "goto", 0, 1, 0x90, assemble_0arg }, 530 { "pcr", 0, 1, 0xA0, assemble_0arg }, 531 { "ptrinc", 0, 1, 0xF0, assemble_0arg }, 532 { "lli", 1, 1, 0x03, assemble_lli }, 533 { "lni", 1, 1, 0x43, assemble_lni }, 534 { "li", 1, 2, 0x43, assemble_li }, 535 { "jmp", 1, 2, 0x83, assemble_jmp }, 536 { "lb", 1, 1, 0x87, assemble_1arg }, 537 { "sb", 1, 1, 0x8B, assemble_1arg }, 538 { "sp", 1, 1, 0x8F, assemble_1arg }, 539 { "cin", 2, 1, 0x02, assemble_cin }, 540 { "mov", 2, 1, 0x00, assemble_mov }, 541 { "cmp", 2, 1, 0xC3, assemble_2arg }, 542 { "or", 3, 1, 0x00, assemble_3arg }, 543 { "nand", 3, 1, 0x01, assemble_3arg }, 544 { "op", 3, 1, 0x02, assemble_3arg } 545 }; 546 547 InstructionDefinition_t* getInstructionFromOpcode(const char *opcode) 548 { 549 InstructionDefinition_t* return_value = NULL; 550 551 int i; 552 for (i = 0; i < TOT_INSTRUCTIONS; i++) 553 { 554 char *label = definitions[i].instructionLabel; 555 if (strlen(label) == strlen(opcode) && 556 strncmp(label, opcode, strlen(label)) == 0) 557 { 558 return_value = &definitions[i]; 559 break; 560 } 561 } 562 563 return return_value; 564 } 565 566 /* char *parse -> parses file and sets label, opcode, and args 567 accordingly */ 568 int parse(int* line_number, FILE *file, char *line, char **label, char **opcode, char *arg[3]) 569 { 570 int success = 0; 571 572 char *str, *first; 573 str = fgets(line, MAX_LINE_LEN, file); 574 575 if (str != NULL) 576 { 577 first = strtok(line, TOKENIZER); 578 579 if (first == NULL || first[0] == '#') 580 { 581 *line_number = *line_number + 1; 582 return parse(line_number, file, line, label, opcode, arg); 583 } 584 else if (first[strlen(first) - 1] == ':') 585 { 586 *label = first; 587 *opcode = strtok(NULL, TOKENIZER); 588 if (*opcode != NULL) 589 { 590 first[strlen(first) - 1] = '\0'; 591 } 592 593 success = 1; 594 } 595 else 596 { 597 *label = NULL; 598 *opcode = first; 599 600 success = 1; 601 } 602 603 if (success) 604 { 605 arg[0] = strtok(NULL, TOKENIZER); 606 arg[1] = strtok(NULL, TOKENIZER); 607 arg[2] = strtok(NULL, TOKENIZER); 608 } 609 610 *line_number = *line_number + 1; 611 } 612 613 return success; 614 } 615 616 /* int labelprocess -> 1st pass over file, defines all labels 617 */ 618 int labelprocess(int line, int *address, char *label, char *opcode, char *arg[3]) 619 { 620 621 int status = 1; 622 623 if (process_label_initial(label, 0) == 0) 624 { 625 printf( 626 "Error:%i: re-use of existing label '%s'\n", 627 line, label ); 628 629 status = 0; 630 } 631 632 return status; 633 } 634 635 /* int preprocess -> 2nd pass over file, links symbols to address 636 and reports syntax errors, fails if returns -1 */ 637 int preprocess(int line, int *address, char *label, char *opcode, char *arg[3]) 638 { 639 int status = 0; 640 641 if (opcode != NULL) 642 { 643 InstructionDefinition_t* ins = getInstructionFromOpcode(opcode); 644 645 if (ins != NULL) 646 { 647 switch (validins(ins, opcode, arg)) 648 { 649 case -1: 650 printf("Error:%i: too few arguements for '%s'\n", 651 line, opcode); 652 status = 0; 653 break; 654 case 1: 655 printf("Error:%i: too many arguements for '%s'\n", 656 line, opcode); 657 status = 0; 658 break; 659 case 0: 660 status = 1; 661 break; 662 default: 663 status = 0; 664 printf("Something Weird!\n"); 665 break; 666 } 667 } 668 else 669 { 670 printf("Error:%i: instruction '%s' does not exist\n", 671 line, opcode); 672 status = 0; 673 } 674 675 process_label_final(label, *address); 676 677 if (status) 678 { 679 uint8_t test_buffer[32]; 680 status = ins->assemble(ins, arg, test_buffer, *address); 681 682 *address = *address + ins->instructionLength; 683 } 684 } 685 else 686 { 687 printf("Error:%i: Unspecified opcode\n", line); 688 status = 0; 689 } 690 691 return status; 692 } 693 694 /* int process -> 3rd pass over file, instructions are turned into 695 machine code with symbols filled in as adresses, 696 fails if returns -1 */ 697 int process( 698 int line, int* address, uint8_t *buffer, char *label, char *opcode, char *arg[3]) 699 { 700 int status = 0; 701 702 InstructionDefinition_t* ins = getInstructionFromOpcode(opcode); 703 704 if (ins != NULL && ins->assemble != NULL) 705 { 706 status = ins->assemble(ins, arg, buffer + *address, *address); 707 } 708 709 if (status) 710 { 711 *address = *address + ins->instructionLength; 712 } 713 714 return status; 715 } 716 717 void print_instruction_header(int label_width) 718 { 719 char* label_str = (char*)malloc(sizeof(char) * (label_width + 1)); 720 for (int i = 0; i < label_width; i++) 721 { 722 label_str[i] = ' '; 723 } 724 label_str[label_width] = '\0'; 725 strncpy(label_str, "label", 5); 726 727 printf("%s line addr:out>op \targs\n", label_str); 728 free(label_str); 729 } 730 731 /* void print_instruction -> prints the instruction with some helpful information 732 about the source line number, the physical address, the label the instruction has, 733 and the args for that instruction 734 */ 735 void print_instruction(int line, int begin_address, int end_address, uint8_t *buffer, char *label, char *opcode, char *arg[3], int label_width) 736 { 737 int len = 0; 738 for (int i = 0; arg[i] != NULL && i < 3; i++) 739 { 740 len += strlen(arg[i]) + 1; 741 } 742 char* arg_str = ""; 743 if (len > 0) 744 { 745 arg_str = (char*)malloc(sizeof(char) * len); 746 747 for (int i = 0; i < len; i++) 748 { 749 arg_str[i] = '\0'; 750 } 751 752 for (int i = 0; i < 3; i++) 753 { 754 if (arg[i] == NULL || arg[i][0] == '#') 755 { 756 break; 757 } 758 sprintf(arg_str, "%s%s\t", arg_str, arg[i]); 759 } 760 } 761 762 char* label_str = (char*)malloc(sizeof(char) * (label_width + 1)); 763 for (int i = 0; i < label_width; i++) 764 { 765 label_str[i] = ' '; 766 } 767 label_str[label_width] = '\0'; 768 769 if (label != NULL) 770 { 771 strncpy(label_str, label, strlen(label)); 772 } 773 774 int this_size = (int)(end_address - begin_address); 775 uint8_t *this_buffer = buffer + begin_address; 776 777 printf("%s %04i %04x:%02x| %s\t%s\n", label_str, line + 1, begin_address, this_buffer[0], opcode, arg_str); 778 for (int i = 1; i < this_size; i++) 779 { 780 printf("%s %04x:%02x| +\n", label_str, begin_address + i, this_buffer[i]); 781 } 782 free(label_str); 783 if (strlen(arg_str) != 0) 784 { 785 free(arg_str); 786 } 787 } 788 789 int output_file(FILE* output, uint8_t* bytes, int size) 790 { 791 fprintf(output, "v2.0 raw"); 792 793 for (int i = 0; i < size; i++) 794 { 795 fprintf(output, "%s%x", (i % 8 == 0) ? "\n" : " ", bytes[i]); 796 } 797 return 1; 798 } 799 800 int main(int argc, char *argv[]) 801 { 802 if (argc != 3) 803 { 804 fprintf(stderr, 805 "do: %s <assembly-code-file> <machine-code-file> \n", 806 argv[0]); 807 808 exit(1); 809 } 810 811 char *input, *output; 812 FILE *inputf = NULL, *outputf = NULL; 813 char *label, *opcodes, *args[3]; 814 char line[MAX_LINE_LEN + 1]; 815 int address = 0, line_number = 0; 816 817 input = argv[1]; 818 inputf = fopen(input, "r"); 819 820 if (inputf == NULL || ferror(inputf)) 821 { 822 printf("Error opening file '%s'\n", input); 823 goto DITCH; 824 } 825 826 output = argv[2]; 827 outputf = fopen(output, "w"); 828 829 if (outputf == NULL || ferror(outputf)) 830 { 831 printf("Error opening file '%s'\n", output); 832 goto DITCH; 833 } 834 printf("Assembling tac file: '%s' TISC %s\n", input, VERSION_STRING); 835 836 while (parse(&line_number, inputf, line, &label, &opcodes, args)) 837 { 838 if (labelprocess(line_number, &address, label, opcodes, args) == 0) 839 { 840 printf("Labelprocess: Error on line #%i\n", line_number); 841 842 goto CLOSEFILES; 843 } 844 } 845 846 address = 0; 847 line_number = 0; 848 849 rewind(inputf); 850 851 while (parse(&line_number, inputf, line, &label, &opcodes, args)) 852 { 853 if (preprocess(line_number, &address, label, opcodes, args) == 0) 854 { 855 printf("Preprocess: Error on line #%i\n", line_number); 856 857 goto CLOSEFILES; 858 } 859 } 860 861 rewind(inputf); 862 863 int full_size = address; 864 865 if (full_size > MAX_PGR_SIZE) 866 { 867 printf("FATAL: Program exceeds maximum size!\n"); 868 printf("FATAL: program size=%i max=%i\n", full_size, MAX_PGR_SIZE); 869 goto CLOSEFILES; 870 } 871 872 uint8_t* w_buffer = (uint8_t*)malloc(sizeof(uint8_t) * full_size); 873 874 if (w_buffer != NULL) 875 { 876 address = 0; 877 line_number = 0; 878 879 int label_width = longest_label(); 880 881 print_instruction_header(label_width); 882 883 while (parse(&line_number, inputf, line, &label, &opcodes, args)) 884 { 885 int begin_address = address; 886 int begin_line = line_number; 887 if (process(line_number, &address, w_buffer, label, opcodes, args) == 0) 888 { 889 printf("Process: Error on line #%i\n", line_number); 890 free(w_buffer); 891 goto CLOSEFILES; 892 } 893 894 print_instruction(begin_line - 1, begin_address, address, w_buffer, label, opcodes, args, label_width); 895 } 896 897 // Output Logisim raw v2.0 format 898 899 printf("Finished assembling tac file: '%s', program size: %i byte%s\n", input, full_size, (full_size > 1) ? "s":""); 900 901 output_file(outputf, w_buffer, full_size); 902 903 free(w_buffer); 904 } 905 906 CLOSEFILES: 907 if (inputf != NULL) fclose(inputf); 908 if (outputf != NULL) fclose(outputf); 909 910 DITCH: 911 return 1; 912 }