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