Initial import from FreeBSD RELENG_4:
[dragonfly.git] / gnu / usr.bin / ptx / ptx.c
1 /* Permuted index for GNU, with keywords in their context.
2    Copyright (C) 1990, 1991, 1993 Free Software Foundation, Inc.
3    Francois Pinard <pinard@iro.umontreal.ca>, 1988.
4
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 2, or (at your option)
8    any later version.
9
10    This program is distributed in the hope that it will be useful, but
11    WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software
17    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 const char *version_string = "GNU ptx version 0.3";
25
26 char *const copyright = "\
27 This program is free software; you can redistribute it and/or modify\n\
28 it under the terms of the GNU General Public License as published by\n\
29 the Free Software Foundation; either version 2, or (at your option)\n\
30 any later version.\n\
31 \n\
32 This program is distributed in the hope that it will be useful,\n\
33 but WITHOUT ANY WARRANTY; without even the implied warranty of\n\
34 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n\
35 GNU General Public License for more details.\n\
36 \n\
37 You should have received a copy of the GNU General Public License\n\
38 along with this program; if not, write to the Free Software\n\
39 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.\n";
40
41 /* Reallocation step when swallowing non regular files.  The value is not
42    the actual reallocation step, but its base two logarithm.  */
43 #define SWALLOW_REALLOC_LOG 12
44
45 /* Imported from "regex.c".  */
46 #define Sword 1
47
48 #ifdef STDC_HEADERS
49
50 #include <stdlib.h>
51 #include <ctype.h>
52
53 #else /* not STDC_HEADERS */
54
55 /* These definitions work, for all 256 characters.  */
56 #define isspace(c) ((c) == ' ' || (c) == '\t' || (c) == '\n')
57 #define isxdigit(c) \
58   (((unsigned char) (c) >= 'a' && (unsigned char) (c) <= 'f')           \
59    || ((unsigned char) (c) >= 'A' && (unsigned char) (c) <= 'F')        \
60    || ((unsigned char) (c) >= '0' && (unsigned char) (c) <= '9'))
61 #define islower(c) ((unsigned char) (c) >= 'a' && (unsigned char) (c) <= 'z')
62 #define isupper(c) ((unsigned char) (c) >= 'A' && (unsigned char) (c) <= 'Z')
63 #define isalpha(c) (islower (c) || isupper (c))
64 #define toupper(c) (islower (c) ? (c) - 'a' + 'A' : (c))
65
66 #endif /* not STDC_HEADERS */
67
68 #if !defined (isascii) || defined (STDC_HEADERS)
69 #undef isascii
70 #define isascii(c) 1
71 #endif
72
73 #define ISXDIGIT(c) (isascii (c) && isxdigit (c))
74 #define ISODIGIT(c) ((c) >= '0' && (c) <= '7')
75 #define HEXTOBIN(c) ((c)>='a'&&(c)<='f' ? (c)-'a'+10 : (c)>='A'&&(c)<='F' ? (c)-'A'+10 : (c)-'0')
76 #define OCTTOBIN(c) ((c) - '0')
77
78 #include <stdio.h>
79 #include <fcntl.h>
80 #include <sys/types.h>
81 #include <sys/stat.h>
82
83 #if !defined(S_ISREG) && defined(S_IFREG)
84 #define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
85 #endif
86
87 #ifdef HAVE_STRING_H
88 #include <string.h>
89 #else /* not HAVE_STRING_H */
90 #include <strings.h>
91 #define strchr index
92 #define strrchr rindex
93 #endif /* not HAVE_STRING_H */
94
95 #include "getopt.h"
96
97 #include <errno.h>
98 #ifndef errno
99 extern int errno;
100 #endif
101
102 #include "bumpalloc.h"
103 #include "diacrit.h"
104 #include "gnuregex.h"
105
106 #ifndef __STDC__
107 void *xmalloc ();
108 void *xrealloc ();
109 #else
110 void *xmalloc (int);
111 void *xrealloc (void *, int);
112 #endif
113
114 \f
115 /* Global definitions.  */
116
117 const char *program_name;       /* name of this program */
118 static int show_help = 0;       /* display usage information and exit */
119 static int show_version = 0;    /* print the version and exit */
120
121 /* Program options.  */
122
123 enum Format
124 {
125   DUMB_FORMAT,                  /* output for a dumb terminal */
126   ROFF_FORMAT,                  /* output for `troff' or `nroff' */
127   TEX_FORMAT,                   /* output for `TeX' or `LaTeX' */
128   UNKNOWN_FORMAT                /* output format still unknown */
129 };
130
131 int gnu_extensions = 1;         /* trigger all GNU extensions */
132 int auto_reference = 0;         /* references are `file_name:line_number:' */
133 int input_reference = 0;        /* references at beginning of input lines */
134 int right_reference = 0;        /* output references after right context  */
135 int line_width = 72;            /* output line width in characters */
136 int gap_size = 3;               /* number of spaces between output fields */
137 const char *truncation_string = "/";
138                                 /* string used to mark line truncations */
139 const char *macro_name = "xx";  /* macro name for roff or TeX output */
140 enum Format output_format = UNKNOWN_FORMAT;
141                                 /* output format */
142
143 int ignore_case = 0;            /* fold lower to upper case for sorting */
144 const char *context_regex_string = NULL;
145                                 /* raw regex for end of context */
146 const char *word_regex_string = NULL;
147                                 /* raw regex for a keyword */
148 const char *break_file = NULL;  /* name of the `Break characters' file */
149 const char *only_file = NULL;   /* name of the `Only words' file */
150 const char *ignore_file = NULL; /* name of the `Ignore words' file */
151
152 /* A BLOCK delimit a region in memory of arbitrary size, like the copy of a
153    whole file.  A WORD is something smaller, its length should fit in a
154    short integer.  A WORD_TABLE may contain several WORDs.  */
155
156 typedef struct
157   {
158     char *start;                /* pointer to beginning of region */
159     char *end;                  /* pointer to end + 1 of region */
160   }
161 BLOCK;
162
163 typedef struct
164   {
165     char *start;                /* pointer to beginning of region */
166     short size;                 /* length of the region */
167   }
168 WORD;
169
170 typedef struct
171   {
172     WORD *start;                /* array of WORDs */
173     size_t length;              /* number of entries */
174   }
175 WORD_TABLE;
176
177 /* Pattern description tables.  */
178
179 /* For each character, provide its folded equivalent.  */
180 unsigned char folded_chars[CHAR_SET_SIZE];
181
182 /* For each character, indicate if it is part of a word.  */
183 char syntax_table[CHAR_SET_SIZE];
184 char *re_syntax_table = syntax_table;
185
186 /* Compiled regex for end of context.  */
187 struct re_pattern_buffer *context_regex;
188
189 /* End of context pattern register indices.  */
190 struct re_registers context_regs;
191
192 /* Compiled regex for a keyword.  */
193 struct re_pattern_buffer *word_regex;
194
195 /* Keyword pattern register indices.  */
196 struct re_registers word_regs;
197
198 /* A word characters fastmap is used only when no word regexp has been
199    provided.  A word is then made up of a sequence of one or more characters
200    allowed by the fastmap.  Contains !0 if character allowed in word.  Not
201    only this is faster in most cases, but it simplifies the implementation
202    of the Break files.  */
203 char word_fastmap[CHAR_SET_SIZE];
204
205 /* Maximum length of any word read.  */
206 int maximum_word_length;
207
208 /* Maximum width of any reference used.  */
209 int reference_max_width;
210
211
212 /* Ignore and Only word tables.  */
213
214 WORD_TABLE ignore_table;        /* table of words to ignore */
215 WORD_TABLE only_table;          /* table of words to select */
216
217 #define ALLOC_NEW_WORD(table) \
218   BUMP_ALLOC ((table)->start, (table)->length, 8, WORD)
219
220 /* Source text table, and scanning macros.  */
221
222 int number_input_files;         /* number of text input files */
223 int total_line_count;           /* total number of lines seen so far */
224 const char **input_file_name;   /* array of text input file names */
225 int *file_line_count;           /* array of `total_line_count' values at end */
226
227 BLOCK text_buffer;              /* file to study */
228 char *text_buffer_maxend;       /* allocated end of text_buffer */
229
230 /* SKIP_NON_WHITE used only for getting or skipping the reference.  */
231
232 #define SKIP_NON_WHITE(cursor, limit)                                   \
233   while (cursor < limit && !isspace(*cursor))                           \
234     cursor++
235
236 #define SKIP_WHITE(cursor, limit)                                       \
237   while (cursor < limit && isspace(*cursor))                            \
238     cursor++
239
240 #define SKIP_WHITE_BACKWARDS(cursor, start)                             \
241   while (cursor > start && isspace(cursor[-1]))                         \
242     cursor--
243
244 #define SKIP_SOMETHING(cursor, limit)                                   \
245   do                                                                    \
246     if (word_regex_string)                                              \
247       {                                                                 \
248         int count;                                                      \
249         count = re_match (word_regex, cursor, limit - cursor, 0, NULL); \
250         cursor += count <= 0 ? 1 : count;                               \
251       }                                                                 \
252     else if (word_fastmap[(unsigned char) *cursor])                     \
253       while (cursor < limit && word_fastmap[(unsigned char) *cursor])   \
254         cursor++;                                                       \
255     else                                                                \
256       cursor++;                                                         \
257   while (0)
258
259 /* Occurrences table.
260
261    The `keyword' pointer provides the central word, which is surrounded
262    by a left context and a right context.  The `keyword' and `length'
263    field allow full 8-bit characters keys, even including NULs.  At other
264    places in this program, the name `keyafter' refers to the keyword
265    followed by its right context.
266
267    The left context does not extend, towards the beginning of the file,
268    further than a distance given by the `left' value.  This value is
269    relative to the keyword beginning, it is usually negative.  This
270    insures that, except for white space, we will never have to backward
271    scan the source text, when it is time to generate the final output
272    lines.
273
274    The right context, indirectly attainable through the keyword end, does
275    not extend, towards the end of the file, further than a distance given
276    by the `right' value.  This value is relative to the keyword
277    beginning, it is usually positive.
278
279    When automatic references are used, the `reference' value is the
280    overall line number in all input files read so far, in this case, it
281    is of type (int).  When input references are used, the `reference'
282    value indicates the distance between the keyword beginning and the
283    start of the reference field, it is of type (DELTA) and usually
284    negative.  */
285
286 typedef short DELTA;            /* to hold displacement within one context */
287
288 typedef struct
289   {
290     WORD key;                   /* description of the keyword */
291     DELTA left;                 /* distance to left context start */
292     DELTA right;                /* distance to right context end */
293     int reference;              /* reference descriptor */
294   }
295 OCCURS;
296
297 /* The various OCCURS tables are indexed by the language.  But the time
298    being, there is no such multiple language support.  */
299
300 OCCURS *occurs_table[1];        /* all words retained from the read text */
301 size_t number_of_occurs[1];     /* number of used slots in occurs_table */
302
303 #define ALLOC_NEW_OCCURS(language) \
304   BUMP_ALLOC (occurs_table[language], number_of_occurs[language], 9, OCCURS)
305
306
307 /* Communication among output routines.  */
308
309 /* Indicate if special output processing is requested for each character.  */
310 char edited_flag[CHAR_SET_SIZE];
311
312 int half_line_width;            /* half of line width, reference excluded */
313 int before_max_width;           /* maximum width of before field */
314 int keyafter_max_width;         /* maximum width of keyword-and-after field */
315 int truncation_string_length;   /* length of string used to flag truncation */
316
317 /* When context is limited by lines, wraparound may happen on final output:
318    the `head' pointer gives access to some supplementary left context which
319    will be seen at the end of the output line, the `tail' pointer gives
320    access to some supplementary right context which will be seen at the
321    beginning of the output line. */
322
323 BLOCK tail;                     /* tail field */
324 int tail_truncation;            /* flag truncation after the tail field */
325
326 BLOCK before;                   /* before field */
327 int before_truncation;          /* flag truncation before the before field */
328
329 BLOCK keyafter;                 /* keyword-and-after field */
330 int keyafter_truncation;        /* flag truncation after the keyafter field */
331
332 BLOCK head;                     /* head field */
333 int head_truncation;            /* flag truncation before the head field */
334
335 BLOCK reference;                /* reference field for input reference mode */
336
337 \f
338 /* Miscellaneous routines.  */
339
340 /*------------------------------------------------------.
341 | Duplicate string STRING, while evaluating \-escapes.  |
342 `------------------------------------------------------*/
343
344 /* Loosely adapted from GNU shellutils printf.c code.  */
345
346 char *
347 copy_unescaped_string (const char *string)
348 {
349   char *result;                 /* allocated result */
350   char *cursor;                 /* cursor in result */
351   int value;                    /* value of \nnn escape */
352   int length;                   /* length of \nnn escape */
353
354   result = xmalloc (strlen (string) + 1);
355   cursor = result;
356
357   while (*string)
358     if (*string == '\\')
359       {
360         string++;
361         switch (*string)
362           {
363           case 'x':             /* \xhhh escape, 3 chars maximum */
364             value = 0;
365             for (length = 0, string++;
366                  length < 3 && ISXDIGIT (*string);
367                  length++, string++)
368               value = value * 16 + HEXTOBIN (*string);
369             if (length == 0)
370               {
371                 *cursor++ = '\\';
372                 *cursor++ = 'x';
373               }
374             else
375               *cursor++ = value;
376             break;
377
378           case '0':             /* \0ooo escape, 3 chars maximum */
379             value = 0;
380             for (length = 0, string++;
381                  length < 3 && ISODIGIT (*string);
382                  length++, string++)
383               value = value * 8 + OCTTOBIN (*string);
384             *cursor++ = value;
385             break;
386
387           case 'a':             /* alert */
388 #if __STDC__
389             *cursor++ = '\a';
390 #else
391             *cursor++ = 7;
392 #endif
393             string++;
394             break;
395
396           case 'b':             /* backspace */
397             *cursor++ = '\b';
398             string++;
399             break;
400
401           case 'c':             /* cancel the rest of the output */
402             while (*string)
403               string++;
404             break;
405
406           case 'f':             /* form feed */
407             *cursor++ = '\f';
408             string++;
409             break;
410
411           case 'n':             /* new line */
412             *cursor++ = '\n';
413             string++;
414             break;
415
416           case 'r':             /* carriage return */
417             *cursor++ = '\r';
418             string++;
419             break;
420
421           case 't':             /* horizontal tab */
422             *cursor++ = '\t';
423             string++;
424             break;
425
426           case 'v':             /* vertical tab */
427 #if __STDC__
428             *cursor++ = '\v';
429 #else
430             *cursor++ = 11;
431 #endif
432             string++;
433             break;
434
435           default:
436             *cursor++ = '\\';
437             *cursor++ = *string++;
438             break;
439           }
440       }
441     else
442       *cursor++ = *string++;
443
444   *cursor = '\0';
445   return result;
446 }
447
448 /*-------------------------------------------------------------------.
449 | Compile the regex represented by STRING, diagnose and abort if any |
450 | error.  Returns the compiled regex structure.                      |
451 `-------------------------------------------------------------------*/
452
453 struct re_pattern_buffer *
454 alloc_and_compile_regex (const char *string)
455 {
456   struct re_pattern_buffer *pattern; /* newly allocated structure */
457   const char *message;          /* error message returned by regex.c */
458
459   pattern = (struct re_pattern_buffer *)
460     xmalloc (sizeof (struct re_pattern_buffer));
461   memset (pattern, 0, sizeof (struct re_pattern_buffer));
462
463   pattern->buffer = NULL;
464   pattern->allocated = 0;
465   pattern->translate = ignore_case ? (char *) folded_chars : NULL;
466   pattern->fastmap = (char *) xmalloc (CHAR_SET_SIZE);
467
468   message = re_compile_pattern (string, strlen (string), pattern);
469   if (message)
470     error (1, 0, "%s (for regexp `%s')", message, string);
471
472   /* The fastmap should be compiled before `re_match'.  The following
473      call is not mandatory, because `re_search' is always called sooner,
474      and it compiles the fastmap if this has not been done yet.  */
475
476   re_compile_fastmap (pattern);
477
478   /* Do not waste extra allocated space.  */
479
480   if (pattern->allocated > pattern->used)
481     {
482       pattern->buffer
483         = (unsigned char *) xrealloc (pattern->buffer, pattern->used);
484       pattern->allocated = pattern->used;
485     }
486
487   return pattern;
488 }
489
490 /*------------------------------------------------------------------------.
491 | This will initialize various tables for pattern match and compiles some |
492 | regexps.                                                                |
493 `------------------------------------------------------------------------*/
494
495 void
496 initialize_regex (void)
497 {
498   int character;                /* character value */
499
500   /* Initialize the regex syntax table.  */
501
502   for (character = 0; character < CHAR_SET_SIZE; character++)
503     syntax_table[character] = isalpha (character) ? Sword : 0;
504
505   /* Initialize the case folding table.  */
506
507   if (ignore_case)
508     for (character = 0; character < CHAR_SET_SIZE; character++)
509       folded_chars[character] = toupper (character);
510
511   /* Unless the user already provided a description of the end of line or
512      end of sentence sequence, select an end of line sequence to compile.
513      If the user provided an empty definition, thus disabling end of line
514      or sentence feature, make it NULL to speed up tests.  If GNU
515      extensions are enabled, use end of sentence like in GNU emacs.  If
516      disabled, use end of lines.  */
517
518   if (context_regex_string)
519     {
520       if (!*context_regex_string)
521         context_regex_string = NULL;
522     }
523   else if (gnu_extensions && !input_reference)
524     context_regex_string = "[.?!][]\"')}]*\\($\\|\t\\|  \\)[ \t\n]*";
525   else
526     context_regex_string = "\n";
527
528   if (context_regex_string)
529     context_regex = alloc_and_compile_regex (context_regex_string);
530
531   /* If the user has already provided a non-empty regexp to describe
532      words, compile it.  Else, unless this has already been done through
533      a user provided Break character file, construct a fastmap of
534      characters that may appear in a word.  If GNU extensions enabled,
535      include only letters of the underlying character set.  If disabled,
536      include almost everything, even punctuations; stop only on white
537      space.  */
538
539   if (word_regex_string && *word_regex_string)
540     word_regex = alloc_and_compile_regex (word_regex_string);
541   else if (!break_file)
542     if (gnu_extensions)
543       {
544
545         /* Simulate \w+.  */
546
547         for (character = 0; character < CHAR_SET_SIZE; character++)
548           word_fastmap[character] = isalpha (character);
549       }
550     else
551       {
552
553         /* Simulate [^ \t\n]+.  */
554
555         memset (word_fastmap, 1, CHAR_SET_SIZE);
556         word_fastmap[' '] = 0;
557         word_fastmap['\t'] = 0;
558         word_fastmap['\n'] = 0;
559       }
560 }
561
562 /*------------------------------------------------------------------------.
563 | This routine will attempt to swallow a whole file name FILE_NAME into a |
564 | contiguous region of memory and return a description of it into BLOCK.  |
565 | Standard input is assumed whenever FILE_NAME is NULL, empty or "-".     |
566 |                                                                         |
567 | Previously, in some cases, white space compression was attempted while  |
568 | inputting text.  This was defeating some regexps like default end of    |
569 | sentence, which checks for two consecutive spaces.  If white space      |
570 | compression is ever reinstated, it should be in output routines.        |
571 `------------------------------------------------------------------------*/
572
573 void
574 swallow_file_in_memory (const char *file_name, BLOCK *block)
575 {
576   int file_handle;              /* file descriptor number */
577   struct stat stat_block;       /* stat block for file */
578   int allocated_length;         /* allocated length of memory buffer */
579   int used_length;              /* used length in memory buffer */
580   int read_length;              /* number of character gotten on last read */
581
582   /* As special cases, a file name which is NULL or "-" indicates standard
583      input, which is already opened.  In all other cases, open the file from
584      its name.  */
585
586   if (!file_name || !*file_name || strcmp (file_name, "-") == 0)
587     file_handle = fileno (stdin);
588   else
589     if ((file_handle = open (file_name, O_RDONLY)) < 0)
590       error (1, errno, file_name);
591
592   /* If the file is a plain, regular file, allocate the memory buffer all at
593      once and swallow the file in one blow.  In other cases, read the file
594      repeatedly in smaller chunks until we have it all, reallocating memory
595      once in a while, as we go.  */
596
597   if (fstat (file_handle, &stat_block) < 0)
598     error (1, errno, file_name);
599
600   if (S_ISREG (stat_block.st_mode))
601     {
602       block->start = (char *) xmalloc ((int) stat_block.st_size);
603
604       if (read (file_handle, block->start, (int) stat_block.st_size)
605           != stat_block.st_size)
606         error (1, errno, file_name);
607
608       block->end = block->start + stat_block.st_size;
609     }
610   else
611     {
612       block->start = (char *) xmalloc (1 << SWALLOW_REALLOC_LOG);
613       used_length = 0;
614       allocated_length = (1 << SWALLOW_REALLOC_LOG);
615
616       while ((read_length = read (file_handle,
617                                   block->start + used_length,
618                                   allocated_length - used_length)) > 0)
619         {
620           used_length += read_length;
621           if (used_length == allocated_length)
622             {
623               allocated_length += (1 << SWALLOW_REALLOC_LOG);
624               block->start
625                 = (char *) xrealloc (block->start, allocated_length);
626             }
627         }
628
629       if (read_length < 0)
630         error (1, errno, file_name);
631
632       block->end = block->start + used_length;
633     }
634
635   /* Close the file, but only if it was not the standard input.  */
636
637   if (file_handle != fileno (stdin))
638     close (file_handle);
639 }
640 \f
641 /* Sort and search routines.  */
642
643 /*--------------------------------------------------------------------------.
644 | Compare two words, FIRST and SECOND, and return 0 if they are identical.  |
645 | Return less than 0 if the first word goes before the second; return       |
646 | greater than 0 if the first word goes after the second.                   |
647 |                                                                           |
648 | If a word is indeed a prefix of the other, the shorter should go first.   |
649 `--------------------------------------------------------------------------*/
650
651 int
652 compare_words (const void *void_first, const void *void_second)
653 {
654 #define first ((WORD *) void_first)
655 #define second ((WORD *) void_second)
656   int length;                   /* minimum of two lengths */
657   int counter;                  /* cursor in words */
658   int value;                    /* value of comparison */
659
660   length = first->size < second->size ? first->size : second->size;
661
662   if (ignore_case)
663     {
664       for (counter = 0; counter < length; counter++)
665         {
666           value = (folded_chars [(unsigned char) (first->start[counter])]
667                    - folded_chars [(unsigned char) (second->start[counter])]);
668           if (value != 0)
669             return value;
670         }
671     }
672   else
673     {
674       for (counter = 0; counter < length; counter++)
675         {
676           value = ((unsigned char) first->start[counter]
677                    - (unsigned char) second->start[counter]);
678           if (value != 0)
679             return value;
680         }
681     }
682
683   return first->size - second->size;
684 #undef first
685 #undef second
686 }
687
688 /*-----------------------------------------------------------------------.
689 | Decides which of two OCCURS, FIRST or SECOND, should lexicographically |
690 | go first.  In case of a tie, preserve the original order through a     |
691 | pointer comparison.                                                    |
692 `-----------------------------------------------------------------------*/
693
694 int
695 compare_occurs (const void *void_first, const void *void_second)
696 {
697 #define first ((OCCURS *) void_first)
698 #define second ((OCCURS *) void_second)
699   int value;
700
701   value = compare_words (&first->key, &second->key);
702   return value == 0 ? first->key.start - second->key.start : value;
703 #undef first
704 #undef second
705 }
706
707 /*------------------------------------------------------------.
708 | Return !0 if WORD appears in TABLE.  Uses a binary search.  |
709 `------------------------------------------------------------*/
710
711 int
712 search_table (WORD *word, WORD_TABLE *table)
713 {
714   int lowest;                   /* current lowest possible index */
715   int highest;                  /* current highest possible index */
716   int middle;                   /* current middle index */
717   int value;                    /* value from last comparison */
718
719   lowest = 0;
720   highest = table->length - 1;
721   while (lowest <= highest)
722     {
723       middle = (lowest + highest) / 2;
724       value = compare_words (word, table->start + middle);
725       if (value < 0)
726         highest = middle - 1;
727       else if (value > 0)
728         lowest = middle + 1;
729       else
730         return 1;
731     }
732   return 0;
733 }
734
735 /*---------------------------------------------------------------------.
736 | Sort the whole occurs table in memory.  Presumably, `qsort' does not |
737 | take intermediate copies or table elements, so the sort will be      |
738 | stabilized throughout the comparison routine.                        |
739 `---------------------------------------------------------------------*/
740
741 void
742 sort_found_occurs (void)
743 {
744
745   /* Only one language for the time being.  */
746
747   qsort (occurs_table[0], number_of_occurs[0], sizeof (OCCURS),
748          compare_occurs);
749 }
750 \f
751 /* Parameter files reading routines.  */
752
753 /*----------------------------------------------------------------------.
754 | Read a file named FILE_NAME, containing a set of break characters.    |
755 | Build a content to the array word_fastmap in which all characters are |
756 | allowed except those found in the file.  Characters may be repeated.  |
757 `----------------------------------------------------------------------*/
758
759 void
760 digest_break_file (const char *file_name)
761 {
762   BLOCK file_contents;          /* to receive a copy of the file */
763   char *cursor;                 /* cursor in file copy */
764
765   swallow_file_in_memory (file_name, &file_contents);
766
767   /* Make the fastmap and record the file contents in it.  */
768
769   memset (word_fastmap, 1, CHAR_SET_SIZE);
770   for (cursor = file_contents.start; cursor < file_contents.end; cursor++)
771     word_fastmap[(unsigned char) *cursor] = 0;
772
773   if (!gnu_extensions)
774     {
775
776       /* If GNU extensions are enabled, the only way to avoid newline as
777          a break character is to write all the break characters in the
778          file with no newline at all, not even at the end of the file.
779          If disabled, spaces, tabs and newlines are always considered as
780          break characters even if not included in the break file.  */
781
782       word_fastmap[' '] = 0;
783       word_fastmap['\t'] = 0;
784       word_fastmap['\n'] = 0;
785     }
786
787   /* Return the space of the file, which is no more required.  */
788
789   free (file_contents.start);
790 }
791
792 /*-----------------------------------------------------------------------.
793 | Read a file named FILE_NAME, containing one word per line, then        |
794 | construct in TABLE a table of WORD descriptors for them.  The routine  |
795 | swallows the whole file in memory; this is at the expense of space     |
796 | needed for newlines, which are useless; however, the reading is fast.  |
797 `-----------------------------------------------------------------------*/
798
799 void
800 digest_word_file (const char *file_name, WORD_TABLE *table)
801 {
802   BLOCK file_contents;          /* to receive a copy of the file */
803   char *cursor;                 /* cursor in file copy */
804   char *word_start;             /* start of the current word */
805
806   swallow_file_in_memory (file_name, &file_contents);
807
808   table->start = NULL;
809   table->length = 0;
810
811   /* Read the whole file.  */
812
813   cursor = file_contents.start;
814   while (cursor < file_contents.end)
815     {
816
817       /* Read one line, and save the word in contains.  */
818
819       word_start = cursor;
820       while (cursor < file_contents.end && *cursor != '\n')
821         cursor++;
822
823       /* Record the word in table if it is not empty.  */
824
825       if (cursor > word_start)
826         {
827           ALLOC_NEW_WORD (table);
828           table->start[table->length].start = word_start;
829           table->start[table->length].size = cursor - word_start;
830           table->length++;
831         }
832
833       /* This test allows for an incomplete line at end of file.  */
834
835       if (cursor < file_contents.end)
836         cursor++;
837     }
838
839   /* Finally, sort all the words read.  */
840
841   qsort (table->start, table->length, (size_t) sizeof (WORD), compare_words);
842 }
843
844 \f
845 /* Keyword recognition and selection.  */
846
847 /*----------------------------------------------------------------------.
848 | For each keyword in the source text, constructs an OCCURS structure.  |
849 `----------------------------------------------------------------------*/
850
851 void
852 find_occurs_in_text (void)
853 {
854   char *cursor;                 /* for scanning the source text */
855   char *scan;                   /* for scanning the source text also */
856   char *line_start;             /* start of the current input line */
857   char *line_scan;              /* newlines scanned until this point */
858   int reference_length;         /* length of reference in input mode */
859   WORD possible_key;            /* possible key, to ease searches */
860   OCCURS *occurs_cursor;        /* current OCCURS under construction */
861
862   char *context_start;          /* start of left context */
863   char *context_end;            /* end of right context */
864   char *word_start;             /* start of word */
865   char *word_end;               /* end of word */
866   char *next_context_start;     /* next start of left context */
867
868   /* reference_length is always used within `if (input_reference)'.
869      However, GNU C diagnoses that it may be used uninitialized.  The
870      following assignment is merely to shut it up.  */
871
872   reference_length = 0;
873
874   /* Tracking where lines start is helpful for reference processing.  In
875      auto reference mode, this allows counting lines.  In input reference
876      mode, this permits finding the beginning of the references.
877
878      The first line begins with the file, skip immediately this very first
879      reference in input reference mode, to help further rejection any word
880      found inside it.  Also, unconditionally assigning these variable has
881      the happy effect of shutting up lint.  */
882
883   line_start = text_buffer.start;
884   line_scan = line_start;
885   if (input_reference)
886     {
887       SKIP_NON_WHITE (line_scan, text_buffer.end);
888       reference_length = line_scan - line_start;
889       SKIP_WHITE (line_scan, text_buffer.end);
890     }
891
892   /* Process the whole buffer, one line or one sentence at a time.  */
893
894   for (cursor = text_buffer.start;
895        cursor < text_buffer.end;
896        cursor = next_context_start)
897     {
898
899       /* `context_start' gets initialized before the processing of each
900          line, or once for the whole buffer if no end of line or sentence
901          sequence separator.  */
902
903       context_start = cursor;
904
905       /* If a end of line or end of sentence sequence is defined and
906          non-empty, `next_context_start' will be recomputed to be the end of
907          each line or sentence, before each one is processed.  If no such
908          sequence, then `next_context_start' is set at the end of the whole
909          buffer, which is then considered to be a single line or sentence.
910          This test also accounts for the case of an incomplete line or
911          sentence at the end of the buffer.  */
912
913       if (context_regex_string
914           && (re_search (context_regex, cursor, text_buffer.end - cursor,
915                          0, text_buffer.end - cursor, &context_regs)
916               >= 0))
917         next_context_start = cursor + context_regs.end[0];
918
919       else
920         next_context_start = text_buffer.end;
921
922       /* Include the separator into the right context, but not any suffix
923          white space in this separator; this insures it will be seen in
924          output and will not take more space than necessary.  */
925
926       context_end = next_context_start;
927       SKIP_WHITE_BACKWARDS (context_end, context_start);
928
929       /* Read and process a single input line or sentence, one word at a
930          time.  */
931
932       while (1)
933         {
934           if (word_regex)
935
936             /* If a word regexp has been compiled, use it to skip at the
937                beginning of the next word.  If there is no such word, exit
938                the loop.  */
939
940             {
941               if (re_search (word_regex, cursor, context_end - cursor,
942                              0, context_end - cursor, &word_regs)
943                   < 0)
944                 break;
945               word_start = cursor + word_regs.start[0];
946               word_end = cursor + word_regs.end[0];
947             }
948           else
949
950             /* Avoid re_search and use the fastmap to skip to the
951                beginning of the next word.  If there is no more word in
952                the buffer, exit the loop.  */
953
954             {
955               scan = cursor;
956               while (scan < context_end
957                      && !word_fastmap[(unsigned char) *scan])
958                 scan++;
959
960               if (scan == context_end)
961                 break;
962
963               word_start = scan;
964
965               while (scan < context_end
966                      && word_fastmap[(unsigned char) *scan])
967                 scan++;
968
969               word_end = scan;
970             }
971
972           /* Skip right to the beginning of the found word.  */
973
974           cursor = word_start;
975
976           /* Skip any zero length word.  Just advance a single position,
977              then go fetch the next word.  */
978
979           if (word_end == word_start)
980             {
981               cursor++;
982               continue;
983             }
984
985           /* This is a genuine, non empty word, so save it as a possible
986              key.  Then skip over it.  Also, maintain the maximum length of
987              all words read so far.  It is mandatory to take the maximum
988              length of all words in the file, without considering if they
989              are actually kept or rejected, because backward jumps at output
990              generation time may fall in *any* word.  */
991
992           possible_key.start = cursor;
993           possible_key.size = word_end - word_start;
994           cursor += possible_key.size;
995
996           if (possible_key.size > maximum_word_length)
997             maximum_word_length = possible_key.size;
998
999           /* In input reference mode, update `line_start' from its previous
1000              value.  Count the lines just in case auto reference mode is
1001              also selected. If it happens that the word just matched is
1002              indeed part of a reference; just ignore it.  */
1003
1004           if (input_reference)
1005             {
1006               while (line_scan < possible_key.start)
1007                 if (*line_scan == '\n')
1008                   {
1009                     total_line_count++;
1010                     line_scan++;
1011                     line_start = line_scan;
1012                     SKIP_NON_WHITE (line_scan, text_buffer.end);
1013                     reference_length = line_scan - line_start;
1014                   }
1015                 else
1016                   line_scan++;
1017               if (line_scan > possible_key.start)
1018                 continue;
1019             }
1020
1021           /* Ignore the word if an `Ignore words' table exists and if it is
1022              part of it.  Also ignore the word if an `Only words' table and
1023              if it is *not* part of it.
1024
1025              It is allowed that both tables be used at once, even if this
1026              may look strange for now.  Just ignore a word that would appear
1027              in both.  If regexps are eventually implemented for these
1028              tables, the Ignore table could then reject words that would
1029              have been previously accepted by the Only table.  */
1030
1031           if (ignore_file && search_table (&possible_key, &ignore_table))
1032             continue;
1033           if (only_file && !search_table (&possible_key, &only_table))
1034             continue;
1035
1036           /* A non-empty word has been found.  First of all, insure
1037              proper allocation of the next OCCURS, and make a pointer to
1038              where it will be constructed.  */
1039
1040           ALLOC_NEW_OCCURS (0);
1041           occurs_cursor = occurs_table[0] + number_of_occurs[0];
1042
1043           /* Define the refence field, if any.  */
1044
1045           if (auto_reference)
1046             {
1047
1048               /* While auto referencing, update `line_start' from its
1049                  previous value, counting lines as we go.  If input
1050                  referencing at the same time, `line_start' has been
1051                  advanced earlier, and the following loop is never really
1052                  executed.  */
1053
1054               while (line_scan < possible_key.start)
1055                 if (*line_scan == '\n')
1056                   {
1057                     total_line_count++;
1058                     line_scan++;
1059                     line_start = line_scan;
1060                     SKIP_NON_WHITE (line_scan, text_buffer.end);
1061                   }
1062                 else
1063                   line_scan++;
1064
1065               occurs_cursor->reference = total_line_count;
1066             }
1067           else if (input_reference)
1068             {
1069
1070               /* If only input referencing, `line_start' has been computed
1071                  earlier to detect the case the word matched would be part
1072                  of the reference.  The reference position is simply the
1073                  value of `line_start'.  */
1074
1075               occurs_cursor->reference
1076                 = (DELTA) (line_start - possible_key.start);
1077               if (reference_length > reference_max_width)
1078                 reference_max_width = reference_length;
1079             }
1080
1081           /* Exclude the reference from the context in simple cases.  */
1082
1083           if (input_reference && line_start == context_start)
1084             {
1085               SKIP_NON_WHITE (context_start, context_end);
1086               SKIP_WHITE (context_start, context_end);
1087             }
1088
1089           /* Completes the OCCURS structure.  */
1090
1091           occurs_cursor->key = possible_key;
1092           occurs_cursor->left = context_start - possible_key.start;
1093           occurs_cursor->right = context_end - possible_key.start;
1094
1095           number_of_occurs[0]++;
1096         }
1097     }
1098 }
1099 \f
1100 /* Formatting and actual output - service routines.  */
1101
1102 /*-----------------------------------------.
1103 | Prints some NUMBER of spaces on stdout.  |
1104 `-----------------------------------------*/
1105
1106 void
1107 print_spaces (int number)
1108 {
1109   int counter;
1110
1111   for (counter = number; counter > 0; counter--)
1112     putchar (' ');
1113 }
1114
1115 /*-------------------------------------.
1116 | Prints the field provided by FIELD.  |
1117 `-------------------------------------*/
1118
1119 void
1120 print_field (BLOCK field)
1121 {
1122   char *cursor;                 /* Cursor in field to print */
1123   int character;                /* Current character */
1124   int base;                     /* Base character, without diacritic */
1125   int diacritic;                /* Diacritic code for the character */
1126
1127   /* Whitespace is not really compressed.  Instead, each white space
1128      character (tab, vt, ht etc.) is printed as one single space.  */
1129
1130   for (cursor = field.start; cursor < field.end; cursor++)
1131     {
1132       character = (unsigned char) *cursor;
1133       if (edited_flag[character])
1134         {
1135
1136           /* First check if this is a diacriticized character.
1137
1138              This works only for TeX.  I do not know how diacriticized
1139              letters work with `roff'.  Please someone explain it to me!  */
1140
1141           diacritic = todiac (character);
1142           if (diacritic != 0 && output_format == TEX_FORMAT)
1143             {
1144               base = tobase (character);
1145               switch (diacritic)
1146                 {
1147
1148                 case 1:         /* Latin diphthongs */
1149                   switch (base)
1150                     {
1151                     case 'o':
1152                       printf ("\\oe{}");
1153                       break;
1154
1155                     case 'O':
1156                       printf ("\\OE{}");
1157                       break;
1158
1159                     case 'a':
1160                       printf ("\\ae{}");
1161                       break;
1162
1163                     case 'A':
1164                       printf ("\\AE{}");
1165                       break;
1166
1167                     default:
1168                       putchar (' ');
1169                     }
1170                   break;
1171
1172                 case 2:         /* Acute accent */
1173                   printf ("\\'%s%c", (base == 'i' ? "\\" : ""), base);
1174                   break;
1175
1176                 case 3:         /* Grave accent */
1177                   printf ("\\`%s%c", (base == 'i' ? "\\" : ""), base);
1178                   break;
1179
1180                 case 4:         /* Circumflex accent */
1181                   printf ("\\^%s%c", (base == 'i' ? "\\" : ""), base);
1182                   break;
1183
1184                 case 5:         /* Diaeresis */
1185                   printf ("\\\"%s%c", (base == 'i' ? "\\" : ""), base);
1186                   break;
1187
1188                 case 6:         /* Tilde accent */
1189                   printf ("\\~%s%c", (base == 'i' ? "\\" : ""), base);
1190                   break;
1191
1192                 case 7:         /* Cedilla */
1193                   printf ("\\c{%c}", base);
1194                   break;
1195
1196                 case 8:         /* Small circle beneath */
1197                   switch (base)
1198                     {
1199                     case 'a':
1200                       printf ("\\aa{}");
1201                       break;
1202
1203                     case 'A':
1204                       printf ("\\AA{}");
1205                       break;
1206
1207                     default:
1208                       putchar (' ');
1209                     }
1210                   break;
1211
1212                 case 9:         /* Strike through */
1213                   switch (base)
1214                     {
1215                     case 'o':
1216                       printf ("\\o{}");
1217                       break;
1218
1219                     case 'O':
1220                       printf ("\\O{}");
1221                       break;
1222
1223                     default:
1224                       putchar (' ');
1225                     }
1226                   break;
1227                 }
1228             }
1229           else
1230
1231             /* This is not a diacritic character, so handle cases which are
1232                really specific to `roff' or TeX.  All white space processing
1233                is done as the default case of this switch.  */
1234
1235             switch (character)
1236               {
1237               case '"':
1238                 /* In roff output format, double any quote.  */
1239                 putchar ('"');
1240                 putchar ('"');
1241                 break;
1242
1243               case '$':
1244               case '%':
1245               case '&':
1246               case '#':
1247               case '_':
1248                 /* In TeX output format, precede these with a backslash.  */
1249                 putchar ('\\');
1250                 putchar (character);
1251                 break;
1252
1253               case '{':
1254               case '}':
1255                 /* In TeX output format, precede these with a backslash and
1256                    force mathematical mode.  */
1257                 printf ("$\\%c$", character);
1258                 break;
1259
1260               case '\\':
1261                 /* In TeX output mode, request production of a backslash.  */
1262                 printf ("\\backslash{}");
1263                 break;
1264
1265               default:
1266                 /* Any other flagged character produces a single space.  */
1267                 putchar (' ');
1268               }
1269         }
1270       else
1271         putchar (*cursor);
1272     }
1273 }
1274
1275 \f
1276 /* Formatting and actual output - planning routines.  */
1277
1278 /*--------------------------------------------------------------------.
1279 | From information collected from command line options and input file |
1280 | readings, compute and fix some output parameter values.             |
1281 `--------------------------------------------------------------------*/
1282
1283 void
1284 fix_output_parameters (void)
1285 {
1286   int file_index;               /* index in text input file arrays */
1287   int line_ordinal;             /* line ordinal value for reference */
1288   char ordinal_string[12];      /* edited line ordinal for reference */
1289   int reference_width;          /* width for the whole reference */
1290   int character;                /* character ordinal */
1291   const char *cursor;           /* cursor in some constant strings */
1292
1293   /* In auto reference mode, the maximum width of this field is
1294      precomputed and subtracted from the overall line width.  Add one for
1295      the column which separate the file name from the line number.  */
1296
1297   if (auto_reference)
1298     {
1299       reference_max_width = 0;
1300       for (file_index = 0; file_index < number_input_files; file_index++)
1301         {
1302           line_ordinal = file_line_count[file_index] + 1;
1303           if (file_index > 0)
1304             line_ordinal -= file_line_count[file_index - 1];
1305           sprintf (ordinal_string, "%d", line_ordinal);
1306           reference_width = strlen (ordinal_string);
1307           if (input_file_name[file_index])
1308             reference_width += strlen (input_file_name[file_index]);
1309           if (reference_width > reference_max_width)
1310             reference_max_width = reference_width;
1311         }
1312       reference_max_width++;
1313       reference.start = (char *) xmalloc (reference_max_width + 1);
1314     }
1315
1316   /* If the reference appears to the left of the output line, reserve some
1317      space for it right away, including one gap size.  */
1318
1319   if ((auto_reference || input_reference) && !right_reference)
1320     line_width -= reference_max_width + gap_size;
1321
1322   /* The output lines, minimally, will contain from left to right a left
1323      context, a gap, and a keyword followed by the right context with no
1324      special intervening gap.  Half of the line width is dedicated to the
1325      left context and the gap, the other half is dedicated to the keyword
1326      and the right context; these values are computed once and for all here.
1327      There also are tail and head wrap around fields, used when the keyword
1328      is near the beginning or the end of the line, or when some long word
1329      cannot fit in, but leave place from wrapped around shorter words.  The
1330      maximum width of these fields are recomputed separately for each line,
1331      on a case by case basis.  It is worth noting that it cannot happen that
1332      both the tail and head fields are used at once.  */
1333
1334   half_line_width = line_width / 2;
1335   before_max_width = half_line_width - gap_size;
1336   keyafter_max_width = half_line_width;
1337
1338   /* If truncation_string is the empty string, make it NULL to speed up
1339      tests.  In this case, truncation_string_length will never get used, so
1340      there is no need to set it.  */
1341
1342   if (truncation_string && *truncation_string)
1343     truncation_string_length = strlen (truncation_string);
1344   else
1345     truncation_string = NULL;
1346
1347   if (gnu_extensions)
1348     {
1349
1350       /* When flagging truncation at the left of the keyword, the
1351          truncation mark goes at the beginning of the before field,
1352          unless there is a head field, in which case the mark goes at the
1353          left of the head field.  When flagging truncation at the right
1354          of the keyword, the mark goes at the end of the keyafter field,
1355          unless there is a tail field, in which case the mark goes at the
1356          end of the tail field.  Only eight combination cases could arise
1357          for truncation marks:
1358
1359          . None.
1360          . One beginning the before field.
1361          . One beginning the head field.
1362          . One ending the keyafter field.
1363          . One ending the tail field.
1364          . One beginning the before field, another ending the keyafter field.
1365          . One ending the tail field, another beginning the before field.
1366          . One ending the keyafter field, another beginning the head field.
1367
1368          So, there is at most two truncation marks, which could appear both
1369          on the left side of the center of the output line, both on the
1370          right side, or one on either side.  */
1371
1372       before_max_width -= 2 * truncation_string_length;
1373       keyafter_max_width -= 2 * truncation_string_length;
1374     }
1375   else
1376     {
1377
1378       /* I never figured out exactly how UNIX' ptx plans the output width
1379          of its various fields.  If GNU extensions are disabled, do not
1380          try computing the field widths correctly; instead, use the
1381          following formula, which does not completely imitate UNIX' ptx,
1382          but almost.  */
1383
1384       keyafter_max_width -= 2 * truncation_string_length + 1;
1385     }
1386
1387   /* Compute which characters need special output processing.  Initialize
1388      by flagging any white space character.  Some systems do not consider
1389      form feed as a space character, but we do.  */
1390
1391   for (character = 0; character < CHAR_SET_SIZE; character++)
1392     edited_flag[character] = isspace (character);
1393   edited_flag['\f'] = 1;
1394
1395   /* Complete the special character flagging according to selected output
1396      format.  */
1397
1398   switch (output_format)
1399     {
1400     case UNKNOWN_FORMAT:
1401       /* Should never happen.  */
1402
1403     case DUMB_FORMAT:
1404       break;
1405
1406     case ROFF_FORMAT:
1407
1408       /* `Quote' characters should be doubled.  */
1409
1410       edited_flag['"'] = 1;
1411       break;
1412
1413     case TEX_FORMAT:
1414
1415       /* Various characters need special processing.  */
1416
1417       for (cursor = "$%&#_{}\\"; *cursor; cursor++)
1418         edited_flag[*cursor] = 1;
1419
1420       /* Any character with 8th bit set will print to a single space, unless
1421          it is diacriticized.  */
1422
1423       for (character = 0200; character < CHAR_SET_SIZE; character++)
1424         edited_flag[character] = todiac (character) != 0;
1425       break;
1426     }
1427 }
1428
1429 /*------------------------------------------------------------------.
1430 | Compute the position and length of all the output fields, given a |
1431 | pointer to some OCCURS.                                           |
1432 `------------------------------------------------------------------*/
1433
1434 void
1435 define_all_fields (OCCURS *occurs)
1436 {
1437   int tail_max_width;           /* allowable width of tail field */
1438   int head_max_width;           /* allowable width of head field */
1439   char *cursor;                 /* running cursor in source text */
1440   char *left_context_start;     /* start of left context */
1441   char *right_context_end;      /* end of right context */
1442   char *left_field_start;       /* conservative start for `head'/`before' */
1443   int file_index;               /* index in text input file arrays */
1444   const char *file_name;                /* file name for reference */
1445   int line_ordinal;             /* line ordinal for reference */
1446
1447   /* Define `keyafter', start of left context and end of right context.
1448      `keyafter' starts at the saved position for keyword and extend to the
1449      right from the end of the keyword, eating separators or full words, but
1450      not beyond maximum allowed width for `keyafter' field or limit for the
1451      right context.  Suffix spaces will be removed afterwards.  */
1452
1453   keyafter.start = occurs->key.start;
1454   keyafter.end = keyafter.start + occurs->key.size;
1455   left_context_start = keyafter.start + occurs->left;
1456   right_context_end = keyafter.start + occurs->right;
1457
1458   cursor = keyafter.end;
1459   while (cursor < right_context_end
1460          && cursor <= keyafter.start + keyafter_max_width)
1461     {
1462       keyafter.end = cursor;
1463       SKIP_SOMETHING (cursor, right_context_end);
1464     }
1465   if (cursor <= keyafter.start + keyafter_max_width)
1466     keyafter.end = cursor;
1467
1468   keyafter_truncation = truncation_string && keyafter.end < right_context_end;
1469
1470   SKIP_WHITE_BACKWARDS (keyafter.end, keyafter.start);
1471
1472   /* When the left context is wide, it might take some time to catch up from
1473      the left context boundary to the beginning of the `head' or `before'
1474      fields.  So, in this case, to speed the catchup, we jump back from the
1475      keyword, using some secure distance, possibly falling in the middle of
1476      a word.  A secure backward jump would be at least half the maximum
1477      width of a line, plus the size of the longest word met in the whole
1478      input.  We conclude this backward jump by a skip forward of at least
1479      one word.  In this manner, we should not inadvertently accept only part
1480      of a word.  From the reached point, when it will be time to fix the
1481      beginning of `head' or `before' fields, we will skip forward words or
1482      delimiters until we get sufficiently near.  */
1483
1484   if (-occurs->left > half_line_width + maximum_word_length)
1485     {
1486       left_field_start
1487         = keyafter.start - (half_line_width + maximum_word_length);
1488       SKIP_SOMETHING (left_field_start, keyafter.start);
1489     }
1490   else
1491     left_field_start = keyafter.start + occurs->left;
1492
1493   /* `before' certainly ends at the keyword, but not including separating
1494      spaces.  It starts after than the saved value for the left context, by
1495      advancing it until it falls inside the maximum allowed width for the
1496      before field.  There will be no prefix spaces either.  `before' only
1497      advances by skipping single separators or whole words. */
1498
1499   before.start = left_field_start;
1500   before.end = keyafter.start;
1501   SKIP_WHITE_BACKWARDS (before.end, before.start);
1502
1503   while (before.start + before_max_width < before.end)
1504     SKIP_SOMETHING (before.start, before.end);
1505
1506   if (truncation_string)
1507     {
1508       cursor = before.start;
1509       SKIP_WHITE_BACKWARDS (cursor, text_buffer.start);
1510       before_truncation = cursor > left_context_start;
1511     }
1512   else
1513     before_truncation = 0;
1514
1515   SKIP_WHITE (before.start, text_buffer.end);
1516
1517   /* The tail could not take more columns than what has been left in the
1518      left context field, and a gap is mandatory.  It starts after the
1519      right context, and does not contain prefixed spaces.  It ends at
1520      the end of line, the end of buffer or when the tail field is full,
1521      whichever comes first.  It cannot contain only part of a word, and
1522      has no suffixed spaces.  */
1523
1524   tail_max_width
1525     = before_max_width - (before.end - before.start) - gap_size;
1526
1527   if (tail_max_width > 0)
1528     {
1529       tail.start = keyafter.end;
1530       SKIP_WHITE (tail.start, text_buffer.end);
1531
1532       tail.end = tail.start;
1533       cursor = tail.end;
1534       while (cursor < right_context_end
1535              && cursor < tail.start + tail_max_width)
1536         {
1537           tail.end = cursor;
1538           SKIP_SOMETHING (cursor, right_context_end);
1539         }
1540
1541       if (cursor < tail.start + tail_max_width)
1542         tail.end = cursor;
1543
1544       if (tail.end > tail.start)
1545         {
1546           keyafter_truncation = 0;
1547           tail_truncation = truncation_string && tail.end < right_context_end;
1548         }
1549       else
1550         tail_truncation = 0;
1551
1552       SKIP_WHITE_BACKWARDS (tail.end, tail.start);
1553     }
1554   else
1555     {
1556
1557       /* No place left for a tail field.  */
1558
1559       tail.start = NULL;
1560       tail.end = NULL;
1561       tail_truncation = 0;
1562     }
1563
1564   /* `head' could not take more columns than what has been left in the right
1565      context field, and a gap is mandatory.  It ends before the left
1566      context, and does not contain suffixed spaces.  Its pointer is advanced
1567      until the head field has shrunk to its allowed width.  It cannot
1568      contain only part of a word, and has no suffixed spaces.  */
1569
1570   head_max_width
1571     = keyafter_max_width - (keyafter.end - keyafter.start) - gap_size;
1572
1573   if (head_max_width > 0)
1574     {
1575       head.end = before.start;
1576       SKIP_WHITE_BACKWARDS (head.end, text_buffer.start);
1577
1578       head.start = left_field_start;
1579       while (head.start + head_max_width < head.end)
1580         SKIP_SOMETHING (head.start, head.end);
1581
1582       if (head.end > head.start)
1583         {
1584           before_truncation = 0;
1585           head_truncation = (truncation_string
1586                              && head.start > left_context_start);
1587         }
1588       else
1589         head_truncation = 0;
1590
1591       SKIP_WHITE (head.start, head.end);
1592     }
1593   else
1594     {
1595
1596       /* No place left for a head field.  */
1597
1598       head.start = NULL;
1599       head.end = NULL;
1600       head_truncation = 0;
1601     }
1602
1603   if (auto_reference)
1604     {
1605
1606       /* Construct the reference text in preallocated space from the file
1607          name and the line number.  Find out in which file the reference
1608          occurred.  Standard input yields an empty file name.  Insure line
1609          numbers are one based, even if they are computed zero based.  */
1610
1611       file_index = 0;
1612       while (file_line_count[file_index] < occurs->reference)
1613         file_index++;
1614
1615       file_name = input_file_name[file_index];
1616       if (!file_name)
1617         file_name = "";
1618
1619       line_ordinal = occurs->reference + 1;
1620       if (file_index > 0)
1621         line_ordinal -= file_line_count[file_index - 1];
1622
1623       sprintf (reference.start, "%s:%d", file_name, line_ordinal);
1624       reference.end = reference.start + strlen (reference.start);
1625     }
1626   else if (input_reference)
1627     {
1628
1629       /* Reference starts at saved position for reference and extends right
1630          until some white space is met.  */
1631
1632       reference.start = keyafter.start + (DELTA) occurs->reference;
1633       reference.end = reference.start;
1634       SKIP_NON_WHITE (reference.end, right_context_end);
1635     }
1636 }
1637
1638 \f
1639 /* Formatting and actual output - control routines.  */
1640
1641 /*----------------------------------------------------------------------.
1642 | Output the current output fields as one line for `troff' or `nroff'.  |
1643 `----------------------------------------------------------------------*/
1644
1645 void
1646 output_one_roff_line (void)
1647 {
1648   /* Output the `tail' field.  */
1649
1650   printf (".%s \"", macro_name);
1651   print_field (tail);
1652   if (tail_truncation)
1653     printf ("%s", truncation_string);
1654   putchar ('"');
1655
1656   /* Output the `before' field.  */
1657
1658   printf (" \"");
1659   if (before_truncation)
1660     printf ("%s", truncation_string);
1661   print_field (before);
1662   putchar ('"');
1663
1664   /* Output the `keyafter' field.  */
1665
1666   printf (" \"");
1667   print_field (keyafter);
1668   if (keyafter_truncation)
1669     printf ("%s", truncation_string);
1670   putchar ('"');
1671
1672   /* Output the `head' field.  */
1673
1674   printf (" \"");
1675   if (head_truncation)
1676     printf ("%s", truncation_string);
1677   print_field (head);
1678   putchar ('"');
1679
1680   /* Conditionally output the `reference' field.  */
1681
1682   if (auto_reference || input_reference)
1683     {
1684       printf (" \"");
1685       print_field (reference);
1686       putchar ('"');
1687     }
1688
1689   putchar ('\n');
1690 }
1691
1692 /*---------------------------------------------------------.
1693 | Output the current output fields as one line for `TeX'.  |
1694 `---------------------------------------------------------*/
1695
1696 void
1697 output_one_tex_line (void)
1698 {
1699   BLOCK key;                    /* key field, isolated */
1700   BLOCK after;                  /* after field, isolated */
1701   char *cursor;                 /* running cursor in source text */
1702
1703   printf ("\\%s ", macro_name);
1704   printf ("{");
1705   print_field (tail);
1706   printf ("}{");
1707   print_field (before);
1708   printf ("}{");
1709   key.start = keyafter.start;
1710   after.end = keyafter.end;
1711   cursor = keyafter.start;
1712   SKIP_SOMETHING (cursor, keyafter.end);
1713   key.end = cursor;
1714   after.start = cursor;
1715   print_field (key);
1716   printf ("}{");
1717   print_field (after);
1718   printf ("}{");
1719   print_field (head);
1720   printf ("}");
1721   if (auto_reference || input_reference)
1722     {
1723       printf ("{");
1724       print_field (reference);
1725       printf ("}");
1726     }
1727   printf ("\n");
1728 }
1729
1730 /*-------------------------------------------------------------------.
1731 | Output the current output fields as one line for a dumb terminal.  |
1732 `-------------------------------------------------------------------*/
1733
1734 void
1735 output_one_dumb_line (void)
1736 {
1737   if (!right_reference)
1738     if (auto_reference)
1739       {
1740
1741         /* Output the `reference' field, in such a way that GNU emacs
1742            next-error will handle it.  The ending colon is taken from the
1743            gap which follows.  */
1744
1745         print_field (reference);
1746         putchar (':');
1747         print_spaces (reference_max_width
1748                       + gap_size
1749                       - (reference.end - reference.start)
1750                       - 1);
1751       }
1752     else
1753       {
1754
1755         /* Output the `reference' field and its following gap.  */
1756
1757         print_field (reference);
1758         print_spaces (reference_max_width
1759                     + gap_size
1760                     - (reference.end - reference.start));
1761       }
1762
1763   if (tail.start < tail.end)
1764     {
1765       /* Output the `tail' field.  */
1766
1767       print_field (tail);
1768       if (tail_truncation)
1769         printf ("%s", truncation_string);
1770
1771       print_spaces (half_line_width - gap_size
1772                     - (before.end - before.start)
1773                     - (before_truncation ? truncation_string_length : 0)
1774                     - (tail.end - tail.start)
1775                     - (tail_truncation ? truncation_string_length : 0));
1776     }
1777   else
1778     print_spaces (half_line_width - gap_size
1779                   - (before.end - before.start)
1780                   - (before_truncation ? truncation_string_length : 0));
1781
1782   /* Output the `before' field.  */
1783
1784   if (before_truncation)
1785     printf ("%s", truncation_string);
1786   print_field (before);
1787
1788   print_spaces (gap_size);
1789
1790   /* Output the `keyafter' field.  */
1791
1792   print_field (keyafter);
1793   if (keyafter_truncation)
1794     printf ("%s", truncation_string);
1795
1796   if (head.start < head.end)
1797     {
1798       /* Output the `head' field.  */
1799
1800       print_spaces (half_line_width
1801                     - (keyafter.end - keyafter.start)
1802                     - (keyafter_truncation ? truncation_string_length : 0)
1803                     - (head.end - head.start)
1804                     - (head_truncation ? truncation_string_length : 0));
1805       if (head_truncation)
1806         printf ("%s", truncation_string);
1807       print_field (head);
1808     }
1809   else
1810
1811     if ((auto_reference || input_reference) && right_reference)
1812       print_spaces (half_line_width
1813                     - (keyafter.end - keyafter.start)
1814                     - (keyafter_truncation ? truncation_string_length : 0));
1815
1816   if ((auto_reference || input_reference) && right_reference)
1817     {
1818       /* Output the `reference' field.  */
1819
1820       print_spaces (gap_size);
1821       print_field (reference);
1822     }
1823
1824   printf ("\n");
1825 }
1826
1827 /*------------------------------------------------------------------------.
1828 | Scan the whole occurs table and, for each entry, output one line in the |
1829 | appropriate format.                                                     |
1830 `------------------------------------------------------------------------*/
1831
1832 void
1833 generate_all_output (void)
1834 {
1835   int occurs_index;             /* index of keyword entry being processed */
1836   OCCURS *occurs_cursor;        /* current keyword entry being processed */
1837
1838
1839   /* The following assignments are useful to provide default values in case
1840      line contexts or references are not used, in which case these variables
1841      would never be computed.  */
1842
1843   tail.start = NULL;
1844   tail.end = NULL;
1845   tail_truncation = 0;
1846
1847   head.start = NULL;
1848   head.end = NULL;
1849   head_truncation = 0;
1850
1851
1852   /* Loop over all keyword occurrences.  */
1853
1854   occurs_cursor = occurs_table[0];
1855
1856   for (occurs_index = 0; occurs_index < number_of_occurs[0]; occurs_index++)
1857     {
1858       /* Compute the exact size of every field and whenever truncation flags
1859          are present or not.  */
1860
1861       define_all_fields (occurs_cursor);
1862
1863       /* Produce one output line according to selected format.  */
1864
1865       switch (output_format)
1866         {
1867         case UNKNOWN_FORMAT:
1868           /* Should never happen.  */
1869
1870         case DUMB_FORMAT:
1871           output_one_dumb_line ();
1872           break;
1873
1874         case ROFF_FORMAT:
1875           output_one_roff_line ();
1876           break;
1877
1878         case TEX_FORMAT:
1879           output_one_tex_line ();
1880           break;
1881         }
1882
1883       /* Advance the cursor into the occurs table.  */
1884
1885       occurs_cursor++;
1886     }
1887 }
1888 \f
1889 /* Option decoding and main program.  */
1890
1891 /*------------------------------------------------------.
1892 | Print program identification and options, then exit.  |
1893 `------------------------------------------------------*/
1894
1895 void
1896 usage (int status)
1897 {
1898   if (status != 0)
1899     fprintf (stderr, "Try `%s --help' for more information.\n", program_name);
1900   else
1901     {
1902       printf ("\
1903 Usage: %s [OPTION]... [INPUT]...   (without -G)\n\
1904   or:  %s -G [OPTION]... [INPUT [OUTPUT]]\n", program_name, program_name);
1905       printf ("\
1906 \n\
1907   -A, --auto-reference           output automatically generated references\n\
1908   -C, --copyright                display Copyright and copying conditions\n\
1909   -G, --traditional              behave more like System V `ptx'\n\
1910   -F, --flag-truncation=STRING   use STRING for flagging line truncations\n\
1911   -M, --macro-name=STRING        macro name to use instead of `xx'\n\
1912   -O, --format=roff              generate output as roff directives\n\
1913   -R, --right-side-refs          put references at right, not counted in -w\n\
1914   -S, --sentence-regexp=REGEXP   for end of lines or end of sentences\n\
1915   -T, --format=tex               generate output as TeX directives\n\
1916   -W, --word-regexp=REGEXP       use REGEXP to match each keyword\n\
1917   -b, --break-file=FILE          word break characters in this FILE\n\
1918   -f, --ignore-case              fold lower case to upper case for sorting\n\
1919   -g, --gap-size=NUMBER          gap size in columns between output fields\n\
1920   -i, --ignore-file=FILE         read ignore word list from FILE\n\
1921   -o, --only-file=FILE           read only word list from this FILE\n\
1922   -r, --references               first field of each line is a reference\n\
1923   -t, --typeset-mode               - not implemented -\n\
1924   -w, --width=NUMBER             output width in columns, reference excluded\n\
1925       --help                     display this help and exit\n\
1926       --version                  output version information and exit\n\
1927 \n\
1928 With no FILE or if FILE is -, read Standard Input.  `-F /' by default.\n");
1929     }
1930   exit (status);
1931 }
1932
1933 /*----------------------------------------------------------------------.
1934 | Main program.  Decode ARGC arguments passed through the ARGV array of |
1935 | strings, then launch execution.                                       |
1936 `----------------------------------------------------------------------*/
1937
1938 /* Long options equivalences.  */
1939 const struct option long_options[] =
1940 {
1941   {"auto-reference", no_argument, NULL, 'A'},
1942   {"break-file", required_argument, NULL, 'b'},
1943   {"copyright", no_argument, NULL, 'C'},
1944   {"flag-truncation", required_argument, NULL, 'F'},
1945   {"ignore-case", no_argument, NULL, 'f'},
1946   {"gap-size", required_argument, NULL, 'g'},
1947   {"help", no_argument, &show_help, 1},
1948   {"ignore-file", required_argument, NULL, 'i'},
1949   {"macro-name", required_argument, NULL, 'M'},
1950   {"only-file", required_argument, NULL, 'o'},
1951   {"references", no_argument, NULL, 'r'},
1952   {"right-side-refs", no_argument, NULL, 'R'},
1953   {"format", required_argument, NULL, 10},
1954   {"sentence-regexp", required_argument, NULL, 'S'},
1955   {"traditional", no_argument, NULL, 'G'},
1956   {"typeset-mode", no_argument, NULL, 't'},
1957   {"version", no_argument, &show_version, 1},
1958   {"width", required_argument, NULL, 'w'},
1959   {"word-regexp", required_argument, NULL, 'W'},
1960   {0, 0, 0, 0},
1961 };
1962
1963 static char const* const format_args[] =
1964 {
1965   "roff", "tex", 0
1966 };
1967
1968 int
1969 main (int argc, char *const argv[])
1970 {
1971   int optchar;                  /* argument character */
1972   extern int optind;            /* index of argument */
1973   extern char *optarg;          /* value or argument */
1974   int file_index;               /* index in text input file arrays */
1975
1976 #ifdef HAVE_MCHECK
1977   /* Use GNU malloc checking.  It has proven to be useful!  */
1978   mcheck ();
1979 #endif /* HAVE_MCHECK */
1980
1981 #ifdef STDC_HEADERS
1982 #ifdef HAVE_SETCHRCLASS
1983   setchrclass (NULL);
1984 #endif
1985 #endif
1986
1987   /* Decode program options.  */
1988
1989   program_name = argv[0];
1990
1991   while ((optchar = getopt_long (argc, argv, "ACF:GM:ORS:TW:b:i:fg:o:trw:",
1992                                  long_options, NULL)),
1993          optchar != EOF)
1994     {
1995       switch (optchar)
1996         {
1997         default:
1998           usage (1);
1999
2000         case 0:
2001           break;
2002
2003         case 'C':
2004           printf ("%s", copyright);
2005           exit (0);
2006
2007         case 'G':
2008           gnu_extensions = 0;
2009           break;
2010
2011         case 'b':
2012           break_file = optarg;
2013           break;
2014
2015         case 'f':
2016           ignore_case = 1;
2017           break;
2018
2019         case 'g':
2020           gap_size = atoi (optarg);
2021           break;
2022
2023         case 'i':
2024           ignore_file = optarg;
2025           break;
2026
2027         case 'o':
2028           only_file = optarg;
2029           break;
2030
2031         case 'r':
2032           input_reference = 1;
2033           break;
2034
2035         case 't':
2036           /* A decouvrir...  */
2037           break;
2038
2039         case 'w':
2040           line_width = atoi (optarg);
2041           break;
2042
2043         case 'A':
2044           auto_reference = 1;
2045           break;
2046
2047         case 'F':
2048           truncation_string = copy_unescaped_string (optarg);
2049           break;
2050
2051         case 'M':
2052           macro_name = optarg;
2053           break;
2054
2055         case 'O':
2056           output_format = ROFF_FORMAT;
2057           break;
2058
2059         case 'R':
2060           right_reference = 1;
2061           break;
2062
2063         case 'S':
2064           context_regex_string = copy_unescaped_string (optarg);
2065           break;
2066
2067         case 'T':
2068           output_format = TEX_FORMAT;
2069           break;
2070
2071         case 'W':
2072           word_regex_string = copy_unescaped_string (optarg);
2073           break;
2074
2075         case 10:
2076           switch (argmatch (optarg, format_args))
2077             {
2078             default:
2079               usage (1);
2080
2081             case 0:
2082               output_format = ROFF_FORMAT;
2083               break;
2084
2085             case 1:
2086               output_format = TEX_FORMAT;
2087               break;
2088             }
2089         }
2090     }
2091
2092   /* Process trivial options.  */
2093
2094   if (show_help)
2095     usage (0);
2096
2097   if (show_version)
2098     {
2099       printf ("%s\n", version_string);
2100       exit (0);
2101     }
2102
2103   /* Change the default Ignore file if one is defined.  */
2104
2105 #ifdef DEFAULT_IGNORE_FILE
2106   if (!ignore_file)
2107     ignore_file = DEFAULT_IGNORE_FILE;
2108 #endif
2109
2110   /* Process remaining arguments.  If GNU extensions are enabled, process
2111      all arguments as input parameters.  If disabled, accept at most two
2112      arguments, the second of which is an output parameter.  */
2113
2114   if (optind == argc)
2115     {
2116
2117       /* No more argument simply means: read standard input.  */
2118
2119       input_file_name = (const char **) xmalloc (sizeof (const char *));
2120       file_line_count = (int *) xmalloc (sizeof (int));
2121       number_input_files = 1;
2122       input_file_name[0] = NULL;
2123     }
2124   else if (gnu_extensions)
2125     {
2126       number_input_files = argc - optind;
2127       input_file_name
2128         = (const char **) xmalloc (number_input_files * sizeof (const char *));
2129       file_line_count
2130         = (int *) xmalloc (number_input_files * sizeof (int));
2131
2132       for (file_index = 0; file_index < number_input_files; file_index++)
2133         {
2134           input_file_name[file_index] = argv[optind];
2135           if (!*argv[optind] || strcmp (argv[optind], "-") == 0)
2136             input_file_name[0] = NULL;
2137           else
2138             input_file_name[0] = argv[optind];
2139           optind++;
2140         }
2141     }
2142   else
2143     {
2144
2145       /* There is one necessary input file.  */
2146
2147       number_input_files = 1;
2148       input_file_name = (const char **) xmalloc (sizeof (const char *));
2149       file_line_count = (int *) xmalloc (sizeof (int));
2150       if (!*argv[optind] || strcmp (argv[optind], "-") == 0)
2151         input_file_name[0] = NULL;
2152       else
2153         input_file_name[0] = argv[optind];
2154       optind++;
2155
2156       /* Redirect standard output, only if requested.  */
2157
2158       if (optind < argc)
2159         {
2160           fclose (stdout);
2161           if (fopen (argv[optind], "w") == NULL)
2162             error (1, errno, argv[optind]);
2163           optind++;
2164         }
2165
2166       /* Diagnose any other argument as an error.  */
2167
2168       if (optind < argc)
2169         usage (1);
2170     }
2171
2172   /* If the output format has not been explicitly selected, choose dumb
2173      terminal format if GNU extensions are enabled, else `roff' format.  */
2174
2175   if (output_format == UNKNOWN_FORMAT)
2176     output_format = gnu_extensions ? DUMB_FORMAT : ROFF_FORMAT;
2177
2178   /* Initialize the main tables.  */
2179
2180   initialize_regex ();
2181
2182   /* Read `Break character' file, if any.  */
2183
2184   if (break_file)
2185     digest_break_file (break_file);
2186
2187   /* Read `Ignore words' file and `Only words' files, if any.  If any of
2188      these files is empty, reset the name of the file to NULL, to avoid
2189      unnecessary calls to search_table. */
2190
2191   if (ignore_file)
2192     {
2193       digest_word_file (ignore_file, &ignore_table);
2194       if (ignore_table.length == 0)
2195         ignore_file = NULL;
2196     }
2197
2198   if (only_file)
2199     {
2200       digest_word_file (only_file, &only_table);
2201       if (only_table.length == 0)
2202         only_file = NULL;
2203     }
2204
2205   /* Prepare to study all the input files.  */
2206
2207   number_of_occurs[0] = 0;
2208   total_line_count = 0;
2209   maximum_word_length = 0;
2210   reference_max_width = 0;
2211
2212   for (file_index = 0; file_index < number_input_files; file_index++)
2213     {
2214
2215       /* Read the file in core, than study it.  */
2216
2217       swallow_file_in_memory (input_file_name[file_index], &text_buffer);
2218       find_occurs_in_text ();
2219
2220       /* Maintain for each file how many lines has been read so far when its
2221          end is reached.  Incrementing the count first is a simple kludge to
2222          handle a possible incomplete line at end of file.  */
2223
2224       total_line_count++;
2225       file_line_count[file_index] = total_line_count;
2226     }
2227
2228   /* Do the output process phase.  */
2229
2230   sort_found_occurs ();
2231   fix_output_parameters ();
2232   generate_all_output ();
2233
2234   /* All done.  */
2235
2236   exit (0);
2237 }