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