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