1 /* Prepare TeX index dribble output into an actual index. 2 $Id: texindex.c,v 1.1.1.3 1997/08/01 22:01:05 kstailey Exp $ 3 4 Copyright (C) 1987, 91, 92, 96, 97 Free Software Foundation, Inc. 5 6 This program is free software; you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation; either version 2, or (at your option) 9 any later version. 10 11 This program is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with this program; if not, write to the Free Software 18 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307. */ 19 20 #include "system.h" 21 #include <getopt.h> 22 23 #if defined (emacs) 24 # include "../src/config.h" 25 /* Some s/os.h files redefine these. */ 26 # undef read 27 # undef close 28 # undef write 29 # undef open 30 #endif 31 32 #if !defined (HAVE_MEMSET) 33 #undef memset 34 #define memset(ptr, ignore, count) bzero (ptr, count) 35 #endif 36 37 38 char *mktemp (); 39 40 #if defined (VMS) 41 # include <file.h> 42 # define TI_NO_ERROR ((1 << 28) | 1) 43 # define TI_FATAL_ERROR ((1 << 28) | 4) 44 # define unlink delete 45 #else /* !VMS */ 46 # if defined (HAVE_SYS_FCNTL_H) 47 # include <sys/types.h> 48 # include <sys/fcntl.h> 49 # endif /* HAVE_SYS_FCNTL_H */ 50 51 # if defined (_AIX) || !defined (_POSIX_VERSION) 52 # include <sys/file.h> 53 # else /* !AIX && _POSIX_VERSION */ 54 # if !defined (HAVE_SYS_FCNTL_H) 55 # include <fcntl.h> 56 # endif /* !HAVE_FCNTL_H */ 57 # endif /* !_AIX && _POSIX_VERSION */ 58 # define TI_NO_ERROR 0 59 # define TI_FATAL_ERROR 1 60 #endif /* !VMS */ 61 62 #if !defined (SEEK_SET) 63 # define SEEK_SET 0 64 # define SEEK_CUR 1 65 # define SEEK_END 2 66 #endif /* !SEEK_SET */ 67 68 /* When sorting in core, this structure describes one line 69 and the position and length of its first keyfield. */ 70 struct lineinfo 71 { 72 char *text; /* The actual text of the line. */ 73 union { 74 char *text; /* The start of the key (for textual comparison). */ 75 long number; /* The numeric value (for numeric comparison). */ 76 } key; 77 long keylen; /* Length of KEY field. */ 78 }; 79 80 /* This structure describes a field to use as a sort key. */ 81 struct keyfield 82 { 83 int startwords; /* Number of words to skip. */ 84 int startchars; /* Number of additional chars to skip. */ 85 int endwords; /* Number of words to ignore at end. */ 86 int endchars; /* Ditto for characters of last word. */ 87 char ignore_blanks; /* Non-zero means ignore spaces and tabs. */ 88 char fold_case; /* Non-zero means case doesn't matter. */ 89 char reverse; /* Non-zero means compare in reverse order. */ 90 char numeric; /* Non-zeros means field is ASCII numeric. */ 91 char positional; /* Sort according to file position. */ 92 char braced; /* Count balanced-braced groupings as fields. */ 93 }; 94 95 /* Vector of keyfields to use. */ 96 struct keyfield keyfields[3]; 97 98 /* Number of keyfields stored in that vector. */ 99 int num_keyfields = 3; 100 101 /* Vector of input file names, terminated with a null pointer. */ 102 char **infiles; 103 104 /* Vector of corresponding output file names, or NULL, meaning default it 105 (add an `s' to the end). */ 106 char **outfiles; 107 108 /* Length of `infiles'. */ 109 int num_infiles; 110 111 /* Pointer to the array of pointers to lines being sorted. */ 112 char **linearray; 113 114 /* The allocated length of `linearray'. */ 115 long nlines; 116 117 /* Directory to use for temporary files. On Unix, it ends with a slash. */ 118 char *tempdir; 119 120 /* Start of filename to use for temporary files. */ 121 char *tempbase; 122 123 /* Number of last temporary file. */ 124 int tempcount; 125 126 /* Number of last temporary file already deleted. 127 Temporary files are deleted by `flush_tempfiles' in order of creation. */ 128 int last_deleted_tempcount; 129 130 /* During in-core sort, this points to the base of the data block 131 which contains all the lines of data. */ 132 char *text_base; 133 134 /* Additional command switches .*/ 135 136 /* Nonzero means do not delete tempfiles -- for debugging. */ 137 int keep_tempfiles; 138 139 /* The name this program was run with. */ 140 char *program_name; 141 142 /* Forward declarations of functions in this file. */ 143 144 void decode_command (); 145 void sort_in_core (); 146 void sort_offline (); 147 char **parsefile (); 148 char *find_field (); 149 char *find_pos (); 150 long find_value (); 151 char *find_braced_pos (); 152 char *find_braced_end (); 153 void writelines (); 154 int compare_field (); 155 int compare_full (); 156 long readline (); 157 int merge_files (); 158 int merge_direct (); 159 void pfatal_with_name (); 160 void fatal (); 161 void error (); 162 void *xmalloc (), *xrealloc (); 163 char *concat (); 164 char *maketempname (); 165 void flush_tempfiles (); 166 char *tempcopy (); 167 168 #define MAX_IN_CORE_SORT 500000 169 170 void 171 main (argc, argv) 172 int argc; 173 char **argv; 174 { 175 int i; 176 177 tempcount = 0; 178 last_deleted_tempcount = 0; 179 180 program_name = strrchr (argv[0], '/'); 181 if (program_name != (char *)NULL) 182 program_name++; 183 else 184 program_name = argv[0]; 185 186 #ifdef HAVE_SETLOCALE 187 /* Set locale via LC_ALL. */ 188 setlocale (LC_ALL, ""); 189 #endif 190 191 /* Set the text message domain. */ 192 bindtextdomain (PACKAGE, LOCALEDIR); 193 textdomain (PACKAGE); 194 195 /* Describe the kind of sorting to do. */ 196 /* The first keyfield uses the first braced field and folds case. */ 197 keyfields[0].braced = 1; 198 keyfields[0].fold_case = 1; 199 keyfields[0].endwords = -1; 200 keyfields[0].endchars = -1; 201 202 /* The second keyfield uses the second braced field, numerically. */ 203 keyfields[1].braced = 1; 204 keyfields[1].numeric = 1; 205 keyfields[1].startwords = 1; 206 keyfields[1].endwords = -1; 207 keyfields[1].endchars = -1; 208 209 /* The third keyfield (which is ignored while discarding duplicates) 210 compares the whole line. */ 211 keyfields[2].endwords = -1; 212 keyfields[2].endchars = -1; 213 214 decode_command (argc, argv); 215 216 tempbase = mktemp (concat ("txiXXXXXX", "", "")); 217 218 /* Process input files completely, one by one. */ 219 220 for (i = 0; i < num_infiles; i++) 221 { 222 int desc; 223 long ptr; 224 char *outfile; 225 226 desc = open (infiles[i], O_RDONLY, 0); 227 if (desc < 0) 228 pfatal_with_name (infiles[i]); 229 lseek (desc, (off_t) 0, SEEK_END); 230 ptr = (long) lseek (desc, (off_t) 0, SEEK_CUR); 231 232 close (desc); 233 234 outfile = outfiles[i]; 235 if (!outfile) 236 { 237 outfile = concat (infiles[i], "s", ""); 238 } 239 240 if (ptr < MAX_IN_CORE_SORT) 241 /* Sort a small amount of data. */ 242 sort_in_core (infiles[i], ptr, outfile); 243 else 244 sort_offline (infiles[i], ptr, outfile); 245 } 246 247 flush_tempfiles (tempcount); 248 exit (TI_NO_ERROR); 249 } 250 251 typedef struct 252 { 253 char *long_name; 254 char *short_name; 255 int *variable_ref; 256 int variable_value; 257 char *arg_name; 258 char *doc_string; 259 } TEXINDEX_OPTION; 260 261 TEXINDEX_OPTION texindex_options[] = { 262 { "--keep", "-k", &keep_tempfiles, 1, (char *)NULL, 263 N_("keep temporary files around after processing") }, 264 { "--no-keep", 0, &keep_tempfiles, 0, (char *)NULL, 265 N_("do not keep temporary files around after processing (default)") }, 266 { "--output", "-o", (int *)NULL, 0, "FILE", 267 N_("send output to FILE") }, 268 { "--version", (char *)NULL, (int *)NULL, 0, (char *)NULL, 269 N_("display version information and exit") }, 270 { "--help", "-h", (int *)NULL, 0, (char *)NULL, 271 N_("display this help and exit") }, 272 { (char *)NULL, (char *)NULL, (int *)NULL, 0, (char *)NULL } 273 }; 274 275 void 276 usage (result_value) 277 int result_value; 278 { 279 register int i; 280 FILE *f = result_value ? stderr : stdout; 281 282 fprintf (f, _("Usage: %s [OPTION]... FILE...\n"), program_name); 283 fprintf (f, _("Generate a sorted index for each TeX output FILE.\n")); 284 /* Avoid trigraph nonsense. */ 285 fprintf (f, _("Usually FILE... is `foo.??\' for a document `foo.texi'.\n")); 286 fprintf (f, _("\nOptions:\n")); 287 288 for (i = 0; texindex_options[i].long_name; i++) 289 { 290 if (texindex_options[i].short_name) 291 fprintf (f, "%s, ", texindex_options[i].short_name); 292 293 fprintf (f, "%s %s", 294 texindex_options[i].long_name, 295 texindex_options[i].arg_name 296 ? texindex_options[i].arg_name : ""); 297 298 fprintf (f, "\t%s\n", _(texindex_options[i].doc_string)); 299 } 300 puts (_("\nEmail bug reports to bug-texinfo@prep.ai.mit.edu.")); 301 302 exit (result_value); 303 } 304 305 /* Decode the command line arguments to set the parameter variables 306 and set up the vector of keyfields and the vector of input files. */ 307 308 void 309 decode_command (argc, argv) 310 int argc; 311 char **argv; 312 { 313 int arg_index = 1; 314 char **ip; 315 char **op; 316 317 /* Store default values into parameter variables. */ 318 319 tempdir = getenv ("TMPDIR"); 320 #ifdef VMS 321 if (tempdir == NULL) 322 tempdir = "sys$scratch:"; 323 #else 324 if (tempdir == NULL) 325 tempdir = "/tmp/"; 326 else 327 tempdir = concat (tempdir, "/", ""); 328 #endif 329 330 keep_tempfiles = 0; 331 332 /* Allocate ARGC input files, which must be enough. */ 333 334 infiles = (char **) xmalloc (argc * sizeof (char *)); 335 outfiles = (char **) xmalloc (argc * sizeof (char *)); 336 ip = infiles; 337 op = outfiles; 338 339 while (arg_index < argc) 340 { 341 char *arg = argv[arg_index++]; 342 343 if (*arg == '-') 344 { 345 if (strcmp (arg, "--version") == 0) 346 { 347 printf (_("texindex (GNU %s %s) 2.1\n"), PACKAGE, VERSION); 348 puts (_("Copyright (C) 1996 Free Software Foundation, Inc.\n\ 349 There is NO warranty. You may redistribute this software\n\ 350 under the terms of the GNU General Public License.\n\ 351 For more information about these matters, see the files named COPYING.")); 352 exit (0); 353 } 354 else if ((strcmp (arg, "--keep") == 0) || 355 (strcmp (arg, "-k") == 0)) 356 { 357 keep_tempfiles = 1; 358 } 359 else if ((strcmp (arg, "--help") == 0) || 360 (strcmp (arg, "-h") == 0)) 361 { 362 usage (0); 363 } 364 else if ((strcmp (arg, "--output") == 0) || 365 (strcmp (arg, "-o") == 0)) 366 { 367 if (argv[arg_index] != (char *)NULL) 368 { 369 arg_index++; 370 if (op > outfiles) 371 *(op - 1) = argv[arg_index]; 372 } 373 else 374 usage (1); 375 } 376 else 377 usage (1); 378 } 379 else 380 { 381 *ip++ = arg; 382 *op++ = (char *)NULL; 383 } 384 } 385 386 /* Record number of keyfields and terminate list of filenames. */ 387 num_infiles = ip - infiles; 388 *ip = (char *)NULL; 389 if (num_infiles == 0) 390 usage (1); 391 } 392 393 /* Return a name for a temporary file. */ 394 395 char * 396 maketempname (count) 397 int count; 398 { 399 char tempsuffix[10]; 400 sprintf (tempsuffix, "%d", count); 401 return concat (tempdir, tempbase, tempsuffix); 402 } 403 404 /* Delete all temporary files up to TO_COUNT. */ 405 406 void 407 flush_tempfiles (to_count) 408 int to_count; 409 { 410 if (keep_tempfiles) 411 return; 412 while (last_deleted_tempcount < to_count) 413 unlink (maketempname (++last_deleted_tempcount)); 414 } 415 416 /* Copy the input file open on IDESC into a temporary file 417 and return the temporary file name. */ 418 419 #define BUFSIZE 1024 420 421 char * 422 tempcopy (idesc) 423 int idesc; 424 { 425 char *outfile = maketempname (++tempcount); 426 int odesc; 427 char buffer[BUFSIZE]; 428 429 odesc = open (outfile, O_WRONLY | O_CREAT, 0666); 430 431 if (odesc < 0) 432 pfatal_with_name (outfile); 433 434 while (1) 435 { 436 int nread = read (idesc, buffer, BUFSIZE); 437 write (odesc, buffer, nread); 438 if (!nread) 439 break; 440 } 441 442 close (odesc); 443 444 return outfile; 445 } 446 447 /* Compare LINE1 and LINE2 according to the specified set of keyfields. */ 448 449 int 450 compare_full (line1, line2) 451 char **line1, **line2; 452 { 453 int i; 454 455 /* Compare using the first keyfield; 456 if that does not distinguish the lines, try the second keyfield; 457 and so on. */ 458 459 for (i = 0; i < num_keyfields; i++) 460 { 461 long length1, length2; 462 char *start1 = find_field (&keyfields[i], *line1, &length1); 463 char *start2 = find_field (&keyfields[i], *line2, &length2); 464 int tem = compare_field (&keyfields[i], start1, length1, *line1 - text_base, 465 start2, length2, *line2 - text_base); 466 if (tem) 467 { 468 if (keyfields[i].reverse) 469 return -tem; 470 return tem; 471 } 472 } 473 474 return 0; /* Lines match exactly. */ 475 } 476 477 /* Compare LINE1 and LINE2, described by structures 478 in which the first keyfield is identified in advance. 479 For positional sorting, assumes that the order of the lines in core 480 reflects their nominal order. */ 481 482 int 483 compare_prepared (line1, line2) 484 struct lineinfo *line1, *line2; 485 { 486 int i; 487 int tem; 488 char *text1, *text2; 489 490 /* Compare using the first keyfield, which has been found for us already. */ 491 if (keyfields->positional) 492 { 493 if (line1->text - text_base > line2->text - text_base) 494 tem = 1; 495 else 496 tem = -1; 497 } 498 else if (keyfields->numeric) 499 tem = line1->key.number - line2->key.number; 500 else 501 tem = compare_field (keyfields, line1->key.text, line1->keylen, 0, 502 line2->key.text, line2->keylen, 0); 503 if (tem) 504 { 505 if (keyfields->reverse) 506 return -tem; 507 return tem; 508 } 509 510 text1 = line1->text; 511 text2 = line2->text; 512 513 /* Compare using the second keyfield; 514 if that does not distinguish the lines, try the third keyfield; 515 and so on. */ 516 517 for (i = 1; i < num_keyfields; i++) 518 { 519 long length1, length2; 520 char *start1 = find_field (&keyfields[i], text1, &length1); 521 char *start2 = find_field (&keyfields[i], text2, &length2); 522 int tem = compare_field (&keyfields[i], start1, length1, text1 - text_base, 523 start2, length2, text2 - text_base); 524 if (tem) 525 { 526 if (keyfields[i].reverse) 527 return -tem; 528 return tem; 529 } 530 } 531 532 return 0; /* Lines match exactly. */ 533 } 534 535 /* Like compare_full but more general. 536 You can pass any strings, and you can say how many keyfields to use. 537 POS1 and POS2 should indicate the nominal positional ordering of 538 the two lines in the input. */ 539 540 int 541 compare_general (str1, str2, pos1, pos2, use_keyfields) 542 char *str1, *str2; 543 long pos1, pos2; 544 int use_keyfields; 545 { 546 int i; 547 548 /* Compare using the first keyfield; 549 if that does not distinguish the lines, try the second keyfield; 550 and so on. */ 551 552 for (i = 0; i < use_keyfields; i++) 553 { 554 long length1, length2; 555 char *start1 = find_field (&keyfields[i], str1, &length1); 556 char *start2 = find_field (&keyfields[i], str2, &length2); 557 int tem = compare_field (&keyfields[i], start1, length1, pos1, 558 start2, length2, pos2); 559 if (tem) 560 { 561 if (keyfields[i].reverse) 562 return -tem; 563 return tem; 564 } 565 } 566 567 return 0; /* Lines match exactly. */ 568 } 569 570 /* Find the start and length of a field in STR according to KEYFIELD. 571 A pointer to the starting character is returned, and the length 572 is stored into the int that LENGTHPTR points to. */ 573 574 char * 575 find_field (keyfield, str, lengthptr) 576 struct keyfield *keyfield; 577 char *str; 578 long *lengthptr; 579 { 580 char *start; 581 char *end; 582 char *(*fun) (); 583 584 if (keyfield->braced) 585 fun = find_braced_pos; 586 else 587 fun = find_pos; 588 589 start = (*fun) (str, keyfield->startwords, keyfield->startchars, 590 keyfield->ignore_blanks); 591 if (keyfield->endwords < 0) 592 { 593 if (keyfield->braced) 594 end = find_braced_end (start); 595 else 596 { 597 end = start; 598 while (*end && *end != '\n') 599 end++; 600 } 601 } 602 else 603 { 604 end = (*fun) (str, keyfield->endwords, keyfield->endchars, 0); 605 if (end - str < start - str) 606 end = start; 607 } 608 *lengthptr = end - start; 609 return start; 610 } 611 612 /* Return a pointer to a specified place within STR, 613 skipping (from the beginning) WORDS words and then CHARS chars. 614 If IGNORE_BLANKS is nonzero, we skip all blanks 615 after finding the specified word. */ 616 617 char * 618 find_pos (str, words, chars, ignore_blanks) 619 char *str; 620 int words, chars; 621 int ignore_blanks; 622 { 623 int i; 624 char *p = str; 625 626 for (i = 0; i < words; i++) 627 { 628 char c; 629 /* Find next bunch of nonblanks and skip them. */ 630 while ((c = *p) == ' ' || c == '\t') 631 p++; 632 while ((c = *p) && c != '\n' && !(c == ' ' || c == '\t')) 633 p++; 634 if (!*p || *p == '\n') 635 return p; 636 } 637 638 while (*p == ' ' || *p == '\t') 639 p++; 640 641 for (i = 0; i < chars; i++) 642 { 643 if (!*p || *p == '\n') 644 break; 645 p++; 646 } 647 return p; 648 } 649 650 /* Like find_pos but assumes that each field is surrounded by braces 651 and that braces within fields are balanced. */ 652 653 char * 654 find_braced_pos (str, words, chars, ignore_blanks) 655 char *str; 656 int words, chars; 657 int ignore_blanks; 658 { 659 int i; 660 int bracelevel; 661 char *p = str; 662 char c; 663 664 for (i = 0; i < words; i++) 665 { 666 bracelevel = 1; 667 while ((c = *p++) != '{' && c != '\n' && c) 668 /* Do nothing. */ ; 669 if (c != '{') 670 return p - 1; 671 while (bracelevel) 672 { 673 c = *p++; 674 if (c == '{') 675 bracelevel++; 676 if (c == '}') 677 bracelevel--; 678 if (c == 0 || c == '\n') 679 return p - 1; 680 } 681 } 682 683 while ((c = *p++) != '{' && c != '\n' && c) 684 /* Do nothing. */ ; 685 686 if (c != '{') 687 return p - 1; 688 689 if (ignore_blanks) 690 while ((c = *p) == ' ' || c == '\t') 691 p++; 692 693 for (i = 0; i < chars; i++) 694 { 695 if (!*p || *p == '\n') 696 break; 697 p++; 698 } 699 return p; 700 } 701 702 /* Find the end of the balanced-brace field which starts at STR. 703 The position returned is just before the closing brace. */ 704 705 char * 706 find_braced_end (str) 707 char *str; 708 { 709 int bracelevel; 710 char *p = str; 711 char c; 712 713 bracelevel = 1; 714 while (bracelevel) 715 { 716 c = *p++; 717 if (c == '{') 718 bracelevel++; 719 if (c == '}') 720 bracelevel--; 721 if (c == 0 || c == '\n') 722 return p - 1; 723 } 724 return p - 1; 725 } 726 727 long 728 find_value (start, length) 729 char *start; 730 long length; 731 { 732 while (length != 0L) 733 { 734 if (isdigit (*start)) 735 return atol (start); 736 length--; 737 start++; 738 } 739 return 0l; 740 } 741 742 /* Vector used to translate characters for comparison. 743 This is how we make all alphanumerics follow all else, 744 and ignore case in the first sorting. */ 745 int char_order[256]; 746 747 void 748 init_char_order () 749 { 750 int i; 751 for (i = 1; i < 256; i++) 752 char_order[i] = i; 753 754 for (i = '0'; i <= '9'; i++) 755 char_order[i] += 512; 756 757 for (i = 'a'; i <= 'z'; i++) 758 { 759 char_order[i] = 512 + i; 760 char_order[i + 'A' - 'a'] = 512 + i; 761 } 762 } 763 764 /* Compare two fields (each specified as a start pointer and a character count) 765 according to KEYFIELD. 766 The sign of the value reports the relation between the fields. */ 767 768 int 769 compare_field (keyfield, start1, length1, pos1, start2, length2, pos2) 770 struct keyfield *keyfield; 771 char *start1; 772 long length1; 773 long pos1; 774 char *start2; 775 long length2; 776 long pos2; 777 { 778 if (keyfields->positional) 779 { 780 if (pos1 > pos2) 781 return 1; 782 else 783 return -1; 784 } 785 if (keyfield->numeric) 786 { 787 long value = find_value (start1, length1) - find_value (start2, length2); 788 if (value > 0) 789 return 1; 790 if (value < 0) 791 return -1; 792 return 0; 793 } 794 else 795 { 796 char *p1 = start1; 797 char *p2 = start2; 798 char *e1 = start1 + length1; 799 char *e2 = start2 + length2; 800 801 while (1) 802 { 803 int c1, c2; 804 805 if (p1 == e1) 806 c1 = 0; 807 else 808 c1 = *p1++; 809 if (p2 == e2) 810 c2 = 0; 811 else 812 c2 = *p2++; 813 814 if (char_order[c1] != char_order[c2]) 815 return char_order[c1] - char_order[c2]; 816 if (!c1) 817 break; 818 } 819 820 /* Strings are equal except possibly for case. */ 821 p1 = start1; 822 p2 = start2; 823 while (1) 824 { 825 int c1, c2; 826 827 if (p1 == e1) 828 c1 = 0; 829 else 830 c1 = *p1++; 831 if (p2 == e2) 832 c2 = 0; 833 else 834 c2 = *p2++; 835 836 if (c1 != c2) 837 /* Reverse sign here so upper case comes out last. */ 838 return c2 - c1; 839 if (!c1) 840 break; 841 } 842 843 return 0; 844 } 845 } 846 847 /* A `struct linebuffer' is a structure which holds a line of text. 848 `readline' reads a line from a stream into a linebuffer 849 and works regardless of the length of the line. */ 850 851 struct linebuffer 852 { 853 long size; 854 char *buffer; 855 }; 856 857 /* Initialize LINEBUFFER for use. */ 858 859 void 860 initbuffer (linebuffer) 861 struct linebuffer *linebuffer; 862 { 863 linebuffer->size = 200; 864 linebuffer->buffer = (char *) xmalloc (200); 865 } 866 867 /* Read a line of text from STREAM into LINEBUFFER. 868 Return the length of the line. */ 869 870 long 871 readline (linebuffer, stream) 872 struct linebuffer *linebuffer; 873 FILE *stream; 874 { 875 char *buffer = linebuffer->buffer; 876 char *p = linebuffer->buffer; 877 char *end = p + linebuffer->size; 878 879 while (1) 880 { 881 int c = getc (stream); 882 if (p == end) 883 { 884 buffer = (char *) xrealloc (buffer, linebuffer->size *= 2); 885 p += buffer - linebuffer->buffer; 886 end += buffer - linebuffer->buffer; 887 linebuffer->buffer = buffer; 888 } 889 if (c < 0 || c == '\n') 890 { 891 *p = 0; 892 break; 893 } 894 *p++ = c; 895 } 896 897 return p - buffer; 898 } 899 900 /* Sort an input file too big to sort in core. */ 901 902 void 903 sort_offline (infile, nfiles, total, outfile) 904 char *infile; 905 int nfiles; 906 long total; 907 char *outfile; 908 { 909 /* More than enough. */ 910 int ntemps = 2 * (total + MAX_IN_CORE_SORT - 1) / MAX_IN_CORE_SORT; 911 char **tempfiles = (char **) xmalloc (ntemps * sizeof (char *)); 912 FILE *istream = fopen (infile, "r"); 913 int i; 914 struct linebuffer lb; 915 long linelength; 916 int failure = 0; 917 918 initbuffer (&lb); 919 920 /* Read in one line of input data. */ 921 922 linelength = readline (&lb, istream); 923 924 if (lb.buffer[0] != '\\' && lb.buffer[0] != '@') 925 { 926 error (_("%s: not a texinfo index file"), infile); 927 return; 928 } 929 930 /* Split up the input into `ntemps' temporary files, or maybe fewer, 931 and put the new files' names into `tempfiles' */ 932 933 for (i = 0; i < ntemps; i++) 934 { 935 char *outname = maketempname (++tempcount); 936 FILE *ostream = fopen (outname, "w"); 937 long tempsize = 0; 938 939 if (!ostream) 940 pfatal_with_name (outname); 941 tempfiles[i] = outname; 942 943 /* Copy lines into this temp file as long as it does not make file 944 "too big" or until there are no more lines. */ 945 946 while (tempsize + linelength + 1 <= MAX_IN_CORE_SORT) 947 { 948 tempsize += linelength + 1; 949 fputs (lb.buffer, ostream); 950 putc ('\n', ostream); 951 952 /* Read another line of input data. */ 953 954 linelength = readline (&lb, istream); 955 if (!linelength && feof (istream)) 956 break; 957 958 if (lb.buffer[0] != '\\' && lb.buffer[0] != '@') 959 { 960 error (_("%s: not a texinfo index file"), infile); 961 failure = 1; 962 goto fail; 963 } 964 } 965 fclose (ostream); 966 if (feof (istream)) 967 break; 968 } 969 970 free (lb.buffer); 971 972 fail: 973 /* Record number of temp files we actually needed. */ 974 975 ntemps = i; 976 977 /* Sort each tempfile into another tempfile. 978 Delete the first set of tempfiles and put the names of the second 979 into `tempfiles'. */ 980 981 for (i = 0; i < ntemps; i++) 982 { 983 char *newtemp = maketempname (++tempcount); 984 sort_in_core (&tempfiles[i], MAX_IN_CORE_SORT, newtemp); 985 if (!keep_tempfiles) 986 unlink (tempfiles[i]); 987 tempfiles[i] = newtemp; 988 } 989 990 if (failure) 991 return; 992 993 /* Merge the tempfiles together and indexify. */ 994 995 merge_files (tempfiles, ntemps, outfile); 996 } 997 998 /* Sort INFILE, whose size is TOTAL, 999 assuming that is small enough to be done in-core, 1000 then indexify it and send the output to OUTFILE (or to stdout). */ 1001 1002 void 1003 sort_in_core (infile, total, outfile) 1004 char *infile; 1005 long total; 1006 char *outfile; 1007 { 1008 char **nextline; 1009 char *data = (char *) xmalloc (total + 1); 1010 char *file_data; 1011 long file_size; 1012 int i; 1013 FILE *ostream = stdout; 1014 struct lineinfo *lineinfo; 1015 1016 /* Read the contents of the file into the moby array `data'. */ 1017 1018 int desc = open (infile, O_RDONLY, 0); 1019 1020 if (desc < 0) 1021 fatal (_("failure reopening %s"), infile); 1022 for (file_size = 0;;) 1023 { 1024 i = read (desc, data + file_size, total - file_size); 1025 if (i <= 0) 1026 break; 1027 file_size += i; 1028 } 1029 file_data = data; 1030 data[file_size] = 0; 1031 1032 close (desc); 1033 1034 if (file_size > 0 && data[0] != '\\' && data[0] != '@') 1035 { 1036 error (_("%s: not a texinfo index file"), infile); 1037 return; 1038 } 1039 1040 init_char_order (); 1041 1042 /* Sort routines want to know this address. */ 1043 1044 text_base = data; 1045 1046 /* Create the array of pointers to lines, with a default size 1047 frequently enough. */ 1048 1049 nlines = total / 50; 1050 if (!nlines) 1051 nlines = 2; 1052 linearray = (char **) xmalloc (nlines * sizeof (char *)); 1053 1054 /* `nextline' points to the next free slot in this array. 1055 `nlines' is the allocated size. */ 1056 1057 nextline = linearray; 1058 1059 /* Parse the input file's data, and make entries for the lines. */ 1060 1061 nextline = parsefile (infile, nextline, file_data, file_size); 1062 if (nextline == 0) 1063 { 1064 error (_("%s: not a texinfo index file"), infile); 1065 return; 1066 } 1067 1068 /* Sort the lines. */ 1069 1070 /* If we have enough space, find the first keyfield of each line in advance. 1071 Make a `struct lineinfo' for each line, which records the keyfield 1072 as well as the line, and sort them. */ 1073 1074 lineinfo = (struct lineinfo *) malloc ((nextline - linearray) * sizeof (struct lineinfo)); 1075 1076 if (lineinfo) 1077 { 1078 struct lineinfo *lp; 1079 char **p; 1080 1081 for (lp = lineinfo, p = linearray; p != nextline; lp++, p++) 1082 { 1083 lp->text = *p; 1084 lp->key.text = find_field (keyfields, *p, &lp->keylen); 1085 if (keyfields->numeric) 1086 lp->key.number = find_value (lp->key.text, lp->keylen); 1087 } 1088 1089 qsort (lineinfo, nextline - linearray, sizeof (struct lineinfo), 1090 compare_prepared); 1091 1092 for (lp = lineinfo, p = linearray; p != nextline; lp++, p++) 1093 *p = lp->text; 1094 1095 free (lineinfo); 1096 } 1097 else 1098 qsort (linearray, nextline - linearray, sizeof (char *), compare_full); 1099 1100 /* Open the output file. */ 1101 1102 if (outfile) 1103 { 1104 ostream = fopen (outfile, "w"); 1105 if (!ostream) 1106 pfatal_with_name (outfile); 1107 } 1108 1109 writelines (linearray, nextline - linearray, ostream); 1110 if (outfile) 1111 fclose (ostream); 1112 1113 free (linearray); 1114 free (data); 1115 } 1116 1117 /* Parse an input string in core into lines. 1118 DATA is the input string, and SIZE is its length. 1119 Data goes in LINEARRAY starting at NEXTLINE. 1120 The value returned is the first entry in LINEARRAY still unused. 1121 Value 0 means input file contents are invalid. */ 1122 1123 char ** 1124 parsefile (filename, nextline, data, size) 1125 char *filename; 1126 char **nextline; 1127 char *data; 1128 long size; 1129 { 1130 char *p, *end; 1131 char **line = nextline; 1132 1133 p = data; 1134 end = p + size; 1135 *end = 0; 1136 1137 while (p != end) 1138 { 1139 if (p[0] != '\\' && p[0] != '@') 1140 return 0; 1141 1142 *line = p; 1143 while (*p && *p != '\n') 1144 p++; 1145 if (p != end) 1146 p++; 1147 1148 line++; 1149 if (line == linearray + nlines) 1150 { 1151 char **old = linearray; 1152 linearray = (char **) xrealloc (linearray, sizeof (char *) * (nlines *= 4)); 1153 line += linearray - old; 1154 } 1155 } 1156 1157 return line; 1158 } 1159 1160 /* Indexification is a filter applied to the sorted lines 1161 as they are being written to the output file. 1162 Multiple entries for the same name, with different page numbers, 1163 get combined into a single entry with multiple page numbers. 1164 The first braced field, which is used for sorting, is discarded. 1165 However, its first character is examined, folded to lower case, 1166 and if it is different from that in the previous line fed to us 1167 a \initial line is written with one argument, the new initial. 1168 1169 If an entry has four braced fields, then the second and third 1170 constitute primary and secondary names. 1171 In this case, each change of primary name 1172 generates a \primary line which contains only the primary name, 1173 and in between these are \secondary lines which contain 1174 just a secondary name and page numbers. */ 1175 1176 /* The last primary name we wrote a \primary entry for. 1177 If only one level of indexing is being done, this is the last name seen. */ 1178 char *lastprimary; 1179 /* Length of storage allocated for lastprimary. */ 1180 int lastprimarylength; 1181 1182 /* Similar, for the secondary name. */ 1183 char *lastsecondary; 1184 int lastsecondarylength; 1185 1186 /* Zero if we are not in the middle of writing an entry. 1187 One if we have written the beginning of an entry but have not 1188 yet written any page numbers into it. 1189 Greater than one if we have written the beginning of an entry 1190 plus at least one page number. */ 1191 int pending; 1192 1193 /* The initial (for sorting purposes) of the last primary entry written. 1194 When this changes, a \initial {c} line is written */ 1195 1196 char *lastinitial; 1197 1198 int lastinitiallength; 1199 1200 /* When we need a string of length 1 for the value of lastinitial, 1201 store it here. */ 1202 1203 char lastinitial1[2]; 1204 1205 /* Initialize static storage for writing an index. */ 1206 1207 void 1208 init_index () 1209 { 1210 pending = 0; 1211 lastinitial = lastinitial1; 1212 lastinitial1[0] = 0; 1213 lastinitial1[1] = 0; 1214 lastinitiallength = 0; 1215 lastprimarylength = 100; 1216 lastprimary = (char *) xmalloc (lastprimarylength + 1); 1217 memset (lastprimary, '\0', lastprimarylength + 1); 1218 lastsecondarylength = 100; 1219 lastsecondary = (char *) xmalloc (lastsecondarylength + 1); 1220 memset (lastsecondary, '\0', lastsecondarylength + 1); 1221 } 1222 1223 /* Indexify. Merge entries for the same name, 1224 insert headers for each initial character, etc. */ 1225 1226 void 1227 indexify (line, ostream) 1228 char *line; 1229 FILE *ostream; 1230 { 1231 char *primary, *secondary, *pagenumber; 1232 int primarylength, secondarylength = 0, pagelength; 1233 int nosecondary; 1234 int initiallength; 1235 char *initial; 1236 char initial1[2]; 1237 register char *p; 1238 1239 /* First, analyze the parts of the entry fed to us this time. */ 1240 1241 p = find_braced_pos (line, 0, 0, 0); 1242 if (*p == '{') 1243 { 1244 initial = p; 1245 /* Get length of inner pair of braces starting at `p', 1246 including that inner pair of braces. */ 1247 initiallength = find_braced_end (p + 1) + 1 - p; 1248 } 1249 else 1250 { 1251 initial = initial1; 1252 initial1[0] = *p; 1253 initial1[1] = 0; 1254 initiallength = 1; 1255 1256 if (initial1[0] >= 'a' && initial1[0] <= 'z') 1257 initial1[0] -= 040; 1258 } 1259 1260 pagenumber = find_braced_pos (line, 1, 0, 0); 1261 pagelength = find_braced_end (pagenumber) - pagenumber; 1262 if (pagelength == 0) 1263 abort (); 1264 1265 primary = find_braced_pos (line, 2, 0, 0); 1266 primarylength = find_braced_end (primary) - primary; 1267 1268 secondary = find_braced_pos (line, 3, 0, 0); 1269 nosecondary = !*secondary; 1270 if (!nosecondary) 1271 secondarylength = find_braced_end (secondary) - secondary; 1272 1273 /* If the primary is different from before, make a new primary entry. */ 1274 if (strncmp (primary, lastprimary, primarylength)) 1275 { 1276 /* Close off current secondary entry first, if one is open. */ 1277 if (pending) 1278 { 1279 fputs ("}\n", ostream); 1280 pending = 0; 1281 } 1282 1283 /* If this primary has a different initial, include an entry for 1284 the initial. */ 1285 if (initiallength != lastinitiallength || 1286 strncmp (initial, lastinitial, initiallength)) 1287 { 1288 fprintf (ostream, "\\initial {"); 1289 fwrite (initial, 1, initiallength, ostream); 1290 fputs ("}\n", ostream); 1291 if (initial == initial1) 1292 { 1293 lastinitial = lastinitial1; 1294 *lastinitial1 = *initial1; 1295 } 1296 else 1297 { 1298 lastinitial = initial; 1299 } 1300 lastinitiallength = initiallength; 1301 } 1302 1303 /* Make the entry for the primary. */ 1304 if (nosecondary) 1305 fputs ("\\entry {", ostream); 1306 else 1307 fputs ("\\primary {", ostream); 1308 fwrite (primary, primarylength, 1, ostream); 1309 if (nosecondary) 1310 { 1311 fputs ("}{", ostream); 1312 pending = 1; 1313 } 1314 else 1315 fputs ("}\n", ostream); 1316 1317 /* Record name of most recent primary. */ 1318 if (lastprimarylength < primarylength) 1319 { 1320 lastprimarylength = primarylength + 100; 1321 lastprimary = (char *) xrealloc (lastprimary, 1322 1 + lastprimarylength); 1323 } 1324 strncpy (lastprimary, primary, primarylength); 1325 lastprimary[primarylength] = 0; 1326 1327 /* There is no current secondary within this primary, now. */ 1328 lastsecondary[0] = 0; 1329 } 1330 1331 /* Should not have an entry with no subtopic following one with a subtopic. */ 1332 1333 if (nosecondary && *lastsecondary) 1334 error (_("entry %s follows an entry with a secondary name"), line); 1335 1336 /* Start a new secondary entry if necessary. */ 1337 if (!nosecondary && strncmp (secondary, lastsecondary, secondarylength)) 1338 { 1339 if (pending) 1340 { 1341 fputs ("}\n", ostream); 1342 pending = 0; 1343 } 1344 1345 /* Write the entry for the secondary. */ 1346 fputs ("\\secondary {", ostream); 1347 fwrite (secondary, secondarylength, 1, ostream); 1348 fputs ("}{", ostream); 1349 pending = 1; 1350 1351 /* Record name of most recent secondary. */ 1352 if (lastsecondarylength < secondarylength) 1353 { 1354 lastsecondarylength = secondarylength + 100; 1355 lastsecondary = (char *) xrealloc (lastsecondary, 1356 1 + lastsecondarylength); 1357 } 1358 strncpy (lastsecondary, secondary, secondarylength); 1359 lastsecondary[secondarylength] = 0; 1360 } 1361 1362 /* Here to add one more page number to the current entry. */ 1363 if (pending++ != 1) 1364 fputs (", ", ostream); /* Punctuate first, if this is not the first. */ 1365 fwrite (pagenumber, pagelength, 1, ostream); 1366 } 1367 1368 /* Close out any unfinished output entry. */ 1369 1370 void 1371 finish_index (ostream) 1372 FILE *ostream; 1373 { 1374 if (pending) 1375 fputs ("}\n", ostream); 1376 free (lastprimary); 1377 free (lastsecondary); 1378 } 1379 1380 /* Copy the lines in the sorted order. 1381 Each line is copied out of the input file it was found in. */ 1382 1383 void 1384 writelines (linearray, nlines, ostream) 1385 char **linearray; 1386 int nlines; 1387 FILE *ostream; 1388 { 1389 char **stop_line = linearray + nlines; 1390 char **next_line; 1391 1392 init_index (); 1393 1394 /* Output the text of the lines, and free the buffer space. */ 1395 1396 for (next_line = linearray; next_line != stop_line; next_line++) 1397 { 1398 /* If -u was specified, output the line only if distinct from previous one. */ 1399 if (next_line == linearray 1400 /* Compare previous line with this one, using only the 1401 explicitly specd keyfields. */ 1402 || compare_general (*(next_line - 1), *next_line, 0L, 0L, num_keyfields - 1)) 1403 { 1404 char *p = *next_line; 1405 char c; 1406 1407 while ((c = *p++) && c != '\n') 1408 /* Do nothing. */ ; 1409 *(p - 1) = 0; 1410 indexify (*next_line, ostream); 1411 } 1412 } 1413 1414 finish_index (ostream); 1415 } 1416 1417 /* Assume (and optionally verify) that each input file is sorted; 1418 merge them and output the result. 1419 Returns nonzero if any input file fails to be sorted. 1420 1421 This is the high-level interface that can handle an unlimited 1422 number of files. */ 1423 1424 #define MAX_DIRECT_MERGE 10 1425 1426 int 1427 merge_files (infiles, nfiles, outfile) 1428 char **infiles; 1429 int nfiles; 1430 char *outfile; 1431 { 1432 char **tempfiles; 1433 int ntemps; 1434 int i; 1435 int value = 0; 1436 int start_tempcount = tempcount; 1437 1438 if (nfiles <= MAX_DIRECT_MERGE) 1439 return merge_direct (infiles, nfiles, outfile); 1440 1441 /* Merge groups of MAX_DIRECT_MERGE input files at a time, 1442 making a temporary file to hold each group's result. */ 1443 1444 ntemps = (nfiles + MAX_DIRECT_MERGE - 1) / MAX_DIRECT_MERGE; 1445 tempfiles = (char **) xmalloc (ntemps * sizeof (char *)); 1446 for (i = 0; i < ntemps; i++) 1447 { 1448 int nf = MAX_DIRECT_MERGE; 1449 if (i + 1 == ntemps) 1450 nf = nfiles - i * MAX_DIRECT_MERGE; 1451 tempfiles[i] = maketempname (++tempcount); 1452 value |= merge_direct (&infiles[i * MAX_DIRECT_MERGE], nf, tempfiles[i]); 1453 } 1454 1455 /* All temporary files that existed before are no longer needed 1456 since their contents have been merged into our new tempfiles. 1457 So delete them. */ 1458 flush_tempfiles (start_tempcount); 1459 1460 /* Now merge the temporary files we created. */ 1461 1462 merge_files (tempfiles, ntemps, outfile); 1463 1464 free (tempfiles); 1465 1466 return value; 1467 } 1468 1469 /* Assume (and optionally verify) that each input file is sorted; 1470 merge them and output the result. 1471 Returns nonzero if any input file fails to be sorted. 1472 1473 This version of merging will not work if the number of 1474 input files gets too high. Higher level functions 1475 use it only with a bounded number of input files. */ 1476 1477 int 1478 merge_direct (infiles, nfiles, outfile) 1479 char **infiles; 1480 int nfiles; 1481 char *outfile; 1482 { 1483 struct linebuffer *lb1, *lb2; 1484 struct linebuffer **thisline, **prevline; 1485 FILE **streams; 1486 int i; 1487 int nleft; 1488 int lossage = 0; 1489 int *file_lossage; 1490 struct linebuffer *prev_out = 0; 1491 FILE *ostream = stdout; 1492 1493 if (outfile) 1494 { 1495 ostream = fopen (outfile, "w"); 1496 } 1497 if (!ostream) 1498 pfatal_with_name (outfile); 1499 1500 init_index (); 1501 1502 if (nfiles == 0) 1503 { 1504 if (outfile) 1505 fclose (ostream); 1506 return 0; 1507 } 1508 1509 /* For each file, make two line buffers. 1510 Also, for each file, there is an element of `thisline' 1511 which points at any time to one of the file's two buffers, 1512 and an element of `prevline' which points to the other buffer. 1513 `thisline' is supposed to point to the next available line from the file, 1514 while `prevline' holds the last file line used, 1515 which is remembered so that we can verify that the file is properly sorted. */ 1516 1517 /* lb1 and lb2 contain one buffer each per file. */ 1518 lb1 = (struct linebuffer *) xmalloc (nfiles * sizeof (struct linebuffer)); 1519 lb2 = (struct linebuffer *) xmalloc (nfiles * sizeof (struct linebuffer)); 1520 1521 /* thisline[i] points to the linebuffer holding the next available line in file i, 1522 or is zero if there are no lines left in that file. */ 1523 thisline = (struct linebuffer **) 1524 xmalloc (nfiles * sizeof (struct linebuffer *)); 1525 /* prevline[i] points to the linebuffer holding the last used line 1526 from file i. This is just for verifying that file i is properly 1527 sorted. */ 1528 prevline = (struct linebuffer **) 1529 xmalloc (nfiles * sizeof (struct linebuffer *)); 1530 /* streams[i] holds the input stream for file i. */ 1531 streams = (FILE **) xmalloc (nfiles * sizeof (FILE *)); 1532 /* file_lossage[i] is nonzero if we already know file i is not 1533 properly sorted. */ 1534 file_lossage = (int *) xmalloc (nfiles * sizeof (int)); 1535 1536 /* Allocate and initialize all that storage. */ 1537 1538 for (i = 0; i < nfiles; i++) 1539 { 1540 initbuffer (&lb1[i]); 1541 initbuffer (&lb2[i]); 1542 thisline[i] = &lb1[i]; 1543 prevline[i] = &lb2[i]; 1544 file_lossage[i] = 0; 1545 streams[i] = fopen (infiles[i], "r"); 1546 if (!streams[i]) 1547 pfatal_with_name (infiles[i]); 1548 1549 readline (thisline[i], streams[i]); 1550 } 1551 1552 /* Keep count of number of files not at eof. */ 1553 nleft = nfiles; 1554 1555 while (nleft) 1556 { 1557 struct linebuffer *best = 0; 1558 struct linebuffer *exch; 1559 int bestfile = -1; 1560 int i; 1561 1562 /* Look at the next avail line of each file; choose the least one. */ 1563 1564 for (i = 0; i < nfiles; i++) 1565 { 1566 if (thisline[i] && 1567 (!best || 1568 0 < compare_general (best->buffer, thisline[i]->buffer, 1569 (long) bestfile, (long) i, num_keyfields))) 1570 { 1571 best = thisline[i]; 1572 bestfile = i; 1573 } 1574 } 1575 1576 /* Output that line, unless it matches the previous one and we 1577 don't want duplicates. */ 1578 1579 if (!(prev_out && 1580 !compare_general (prev_out->buffer, 1581 best->buffer, 0L, 1L, num_keyfields - 1))) 1582 indexify (best->buffer, ostream); 1583 prev_out = best; 1584 1585 /* Now make the line the previous of its file, and fetch a new 1586 line from that file. */ 1587 1588 exch = prevline[bestfile]; 1589 prevline[bestfile] = thisline[bestfile]; 1590 thisline[bestfile] = exch; 1591 1592 while (1) 1593 { 1594 /* If the file has no more, mark it empty. */ 1595 1596 if (feof (streams[bestfile])) 1597 { 1598 thisline[bestfile] = 0; 1599 /* Update the number of files still not empty. */ 1600 nleft--; 1601 break; 1602 } 1603 readline (thisline[bestfile], streams[bestfile]); 1604 if (thisline[bestfile]->buffer[0] || !feof (streams[bestfile])) 1605 break; 1606 } 1607 } 1608 1609 finish_index (ostream); 1610 1611 /* Free all storage and close all input streams. */ 1612 1613 for (i = 0; i < nfiles; i++) 1614 { 1615 fclose (streams[i]); 1616 free (lb1[i].buffer); 1617 free (lb2[i].buffer); 1618 } 1619 free (file_lossage); 1620 free (lb1); 1621 free (lb2); 1622 free (thisline); 1623 free (prevline); 1624 free (streams); 1625 1626 if (outfile) 1627 fclose (ostream); 1628 1629 return lossage; 1630 } 1631 1632 /* Print error message and exit. */ 1633 1634 void 1635 fatal (format, arg) 1636 char *format, *arg; 1637 { 1638 error (format, arg); 1639 exit (TI_FATAL_ERROR); 1640 } 1641 1642 /* Print error message. FORMAT is printf control string, ARG is arg for it. */ 1643 void 1644 error (format, arg) 1645 char *format, *arg; 1646 { 1647 printf ("%s: ", program_name); 1648 printf (format, arg); 1649 if (format[strlen (format) -1] != '\n') 1650 printf ("\n"); 1651 } 1652 1653 void 1654 perror_with_name (name) 1655 char *name; 1656 { 1657 char *s; 1658 1659 s = strerror (errno); 1660 printf ("%s: ", program_name); 1661 printf ("%s; for file `%s'.\n", s, name); 1662 } 1663 1664 void 1665 pfatal_with_name (name) 1666 char *name; 1667 { 1668 char *s; 1669 1670 s = strerror (errno); 1671 printf ("%s: ", program_name); 1672 printf (_("%s; for file `%s'.\n"), s, name); 1673 exit (TI_FATAL_ERROR); 1674 } 1675 1676 /* Return a newly-allocated string whose contents concatenate those of 1677 S1, S2, S3. */ 1678 1679 char * 1680 concat (s1, s2, s3) 1681 char *s1, *s2, *s3; 1682 { 1683 int len1 = strlen (s1), len2 = strlen (s2), len3 = strlen (s3); 1684 char *result = (char *) xmalloc (len1 + len2 + len3 + 1); 1685 1686 strcpy (result, s1); 1687 strcpy (result + len1, s2); 1688 strcpy (result + len1 + len2, s3); 1689 *(result + len1 + len2 + len3) = 0; 1690 1691 return result; 1692 } 1693 1694 #if !defined (HAVE_STRERROR) 1695 extern char *sys_errlist[]; 1696 extern int sys_nerr; 1697 1698 char * 1699 strerror (num) 1700 int num; 1701 { 1702 if (num >= sys_nerr) 1703 return (""); 1704 else 1705 return (sys_errlist[num]); 1706 } 1707 #endif /* !HAVE_STRERROR */ 1708 1709 #if !defined (HAVE_STRCHR) 1710 char * 1711 strrchr (string, character) 1712 char *string; 1713 int character; 1714 { 1715 register int i; 1716 1717 for (i = strlen (string) - 1; i > -1; i--) 1718 if (string[i] == character) 1719 return (string + i); 1720 1721 return ((char *)NULL); 1722 } 1723 #endif /* HAVE_STRCHR */ 1724 1725 void 1726 memory_error (callers_name, bytes_wanted) 1727 char *callers_name; 1728 int bytes_wanted; 1729 { 1730 char printable_string[80]; 1731 1732 sprintf (printable_string, 1733 _("Virtual memory exhausted in %s ()! Needed %d bytes."), 1734 callers_name, bytes_wanted); 1735 1736 error (printable_string); 1737 abort (); 1738 } 1739 1740 /* Just like malloc, but kills the program in case of fatal error. */ 1741 void * 1742 xmalloc (nbytes) 1743 int nbytes; 1744 { 1745 void *temp = (void *) malloc (nbytes); 1746 1747 if (nbytes && temp == (void *)NULL) 1748 memory_error ("xmalloc", nbytes); 1749 1750 return (temp); 1751 } 1752 1753 /* Like realloc (), but barfs if there isn't enough memory. */ 1754 void * 1755 xrealloc (pointer, nbytes) 1756 void *pointer; 1757 int nbytes; 1758 { 1759 void *temp; 1760 1761 if (!pointer) 1762 temp = (void *)xmalloc (nbytes); 1763 else 1764 temp = (void *)realloc (pointer, nbytes); 1765 1766 if (nbytes && !temp) 1767 memory_error ("xrealloc", nbytes); 1768 1769 return (temp); 1770 } 1771