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