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