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