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