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