*** empty log message ***
[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 "regex.h"
32
33 /* Number of possible characters in a byte.  */
34 #define CHAR_SET_SIZE 256
35
36 /* The ctype definitions should work for all 256 characters.  */
37 #if STDC_HEADERS
38 # include <ctype.h>
39 #else
40 # define isspace(C) ((C) == ' ' || (C) == '\t' || (C) == '\n')
41 # define isxdigit(C) \
42   (((unsigned char) (C) >= 'a' && (unsigned char) (C) <= 'f')           \
43    || ((unsigned char) (C) >= 'A' && (unsigned char) (C) <= 'F')        \
44    || ((unsigned char) (C) >= '0' && (unsigned char) (C) <= '9'))
45 # define islower(C) ((unsigned char) (C) >= 'a' && (unsigned char) (C) <= 'z')
46 # define isupper(C) ((unsigned char) (C) >= 'A' && (unsigned char) (C) <= 'Z')
47 # define isalpha(C) (islower (C) || isupper (C))
48 # define toupper(C) (islower (C) ? (C) - 'a' + 'A' : (C))
49 #endif
50
51 #if !defined (isascii) || defined (STDC_HEADERS)
52 # undef isascii
53 # define isascii(C) 1
54 #endif
55
56 #ifndef ISXDIGIT
57 # define ISXDIGIT(C) (isascii (C) && isxdigit (C))
58 #endif
59 #define ISODIGIT(C) ((C) >= '0' && (C) <= '7')
60 #define HEXTOBIN(C) ((C) >= 'a' && (C) <= 'f' ? (C)-'a'+10 \
61                      : (C) >= 'A' && (C) <= 'F' ? (C)-'A'+10 : (C)-'0')
62 #define OCTTOBIN(C) ((C) - '0')
63
64 /* Debugging the memory allocator.  */
65
66 #if WITH_DMALLOC
67 # define MALLOC_FUNC_CHECK 1
68 # include <dmalloc.h>
69 #endif
70 \f
71 /* Global definitions.  */
72
73 /* Reallocation step when swallowing non regular files.  The value is not
74    the actual reallocation step, but its base two logarithm.  */
75 #define SWALLOW_REALLOC_LOG 12
76
77 /* Imported from "regex.c".  */
78 #define Sword 1
79
80 /* The name this program was run with. */
81 char *program_name;
82
83 /* If nonzero, display usage information and exit.  */
84 static int show_help = 0;
85
86 /* If nonzero, print the version on standard output and exit.  */
87 static int show_version = 0;
88
89 /* Program options.  */
90
91 enum Format
92 {
93   UNKNOWN_FORMAT,               /* output format still unknown */
94   DUMB_FORMAT,                  /* output for a dumb terminal */
95   ROFF_FORMAT,                  /* output for `troff' or `nroff' */
96   TEX_FORMAT                    /* output for `TeX' or `LaTeX' */
97 };
98
99 int gnu_extensions = 1;         /* trigger all GNU extensions */
100 int auto_reference = 0;         /* references are `file_name:line_number:' */
101 int input_reference = 0;        /* references at beginning of input lines */
102 int right_reference = 0;        /* output references after right context  */
103 int line_width = 72;            /* output line width in characters */
104 int gap_size = 3;               /* number of spaces between output fields */
105 const char *truncation_string = "/";
106                                 /* string used to mark line truncations */
107 const char *macro_name = "xx";  /* macro name for roff or TeX output */
108 enum Format output_format = UNKNOWN_FORMAT;
109                                 /* output format */
110
111 int ignore_case = 0;            /* fold lower to upper case for sorting */
112 const char *context_regex_string = NULL;
113                                 /* raw regex for end of context */
114 const char *word_regex_string = NULL;
115                                 /* raw regex for a keyword */
116 const char *break_file = NULL;  /* name of the `Break characters' file */
117 const char *only_file = NULL;   /* name of the `Only words' file */
118 const char *ignore_file = NULL; /* name of the `Ignore words' file */
119
120 /* A BLOCK delimit a region in memory of arbitrary size, like the copy of a
121    whole file.  A WORD is something smaller, its length should fit in a
122    short integer.  A WORD_TABLE may contain several WORDs.  */
123
124 typedef struct
125   {
126     char *start;                /* pointer to beginning of region */
127     char *end;                  /* pointer to end + 1 of region */
128   }
129 BLOCK;
130
131 typedef struct
132   {
133     char *start;                /* pointer to beginning of region */
134     short size;                 /* length of the region */
135   }
136 WORD;
137
138 typedef struct
139   {
140     WORD *start;                /* array of WORDs */
141     size_t length;              /* number of entries */
142   }
143 WORD_TABLE;
144
145 /* Pattern description tables.  */
146
147 /* For each character, provide its folded equivalent.  */
148 unsigned char folded_chars[CHAR_SET_SIZE];
149
150 /* For each character, indicate if it is part of a word.  */
151 char syntax_table[CHAR_SET_SIZE];
152 char *re_syntax_table = syntax_table;
153
154 /* Compiled regex for end of context.  */
155 struct re_pattern_buffer *context_regex;
156
157 /* End of context pattern register indices.  */
158 struct re_registers context_regs;
159
160 /* Compiled regex for a keyword.  */
161 struct re_pattern_buffer *word_regex;
162
163 /* Keyword pattern register indices.  */
164 struct re_registers word_regs;
165
166 /* A word characters fastmap is used only when no word regexp has been
167    provided.  A word is then made up of a sequence of one or more characters
168    allowed by the fastmap.  Contains !0 if character allowed in word.  Not
169    only this is faster in most cases, but it simplifies the implementation
170    of the Break files.  */
171 char word_fastmap[CHAR_SET_SIZE];
172
173 /* Maximum length of any word read.  */
174 int maximum_word_length;
175
176 /* Maximum width of any reference used.  */
177 int reference_max_width;
178
179 /* 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, 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, file_name);
564
565 #if !MSDOS
566
567   /* On MSDOS, we cannot predict in memory size from file size, because of
568      end of line conversions.  */
569
570   if (S_ISREG (stat_block.st_mode))
571     {
572       block->start = (char *) xmalloc ((size_t) stat_block.st_size);
573
574       if (read (file_handle, block->start, (size_t) stat_block.st_size)
575           != stat_block.st_size)
576         error (EXIT_FAILURE, errno, file_name);
577
578       block->end = block->start + stat_block.st_size;
579     }
580   else
581
582 #endif /* not MSDOS */
583
584     {
585       block->start = (char *) xmalloc ((size_t) 1 << SWALLOW_REALLOC_LOG);
586       used_length = 0;
587       allocated_length = (1 << SWALLOW_REALLOC_LOG);
588
589       while (read_length = read (file_handle,
590                                  block->start + used_length,
591                                  allocated_length - used_length),
592              read_length > 0)
593         {
594           used_length += read_length;
595           if (used_length == allocated_length)
596             {
597               allocated_length += (1 << SWALLOW_REALLOC_LOG);
598               block->start
599                 = (char *) xrealloc (block->start, allocated_length);
600             }
601         }
602
603       if (read_length < 0)
604         error (EXIT_FAILURE, errno, file_name);
605
606       block->end = block->start + used_length;
607     }
608
609   /* Close the file, but only if it was not the standard input.  */
610
611   if (file_handle != fileno (stdin))
612     close (file_handle);
613 }
614 \f
615 /* Sort and search routines.  */
616
617 /*--------------------------------------------------------------------------.
618 | Compare two words, FIRST and SECOND, and return 0 if they are identical.  |
619 | Return less than 0 if the first word goes before the second; return       |
620 | greater than 0 if the first word goes after the second.                   |
621 |                                                                           |
622 | If a word is indeed a prefix of the other, the shorter should go first.   |
623 `--------------------------------------------------------------------------*/
624
625 static int
626 compare_words (const void *void_first, const void *void_second)
627 {
628 #define first ((const WORD *) void_first)
629 #define second ((const WORD *) void_second)
630   int length;                   /* minimum of two lengths */
631   int counter;                  /* cursor in words */
632   int value;                    /* value of comparison */
633
634   length = first->size < second->size ? first->size : second->size;
635
636   if (ignore_case)
637     {
638       for (counter = 0; counter < length; counter++)
639         {
640           value = (folded_chars [(unsigned char) (first->start[counter])]
641                    - folded_chars [(unsigned char) (second->start[counter])]);
642           if (value != 0)
643             return value;
644         }
645     }
646   else
647     {
648       for (counter = 0; counter < length; counter++)
649         {
650           value = ((unsigned char) first->start[counter]
651                    - (unsigned char) second->start[counter]);
652           if (value != 0)
653             return value;
654         }
655     }
656
657   return first->size - second->size;
658 #undef first
659 #undef second
660 }
661
662 /*-----------------------------------------------------------------------.
663 | Decides which of two OCCURS, FIRST or SECOND, should lexicographically |
664 | go first.  In case of a tie, preserve the original order through a     |
665 | pointer comparison.                                                    |
666 `-----------------------------------------------------------------------*/
667
668 static int
669 compare_occurs (const void *void_first, const void *void_second)
670 {
671 #define first ((const OCCURS *) void_first)
672 #define second ((const OCCURS *) void_second)
673   int value;
674
675   value = compare_words (&first->key, &second->key);
676   return value == 0 ? first->key.start - second->key.start : value;
677 #undef first
678 #undef second
679 }
680
681 /*------------------------------------------------------------.
682 | Return !0 if WORD appears in TABLE.  Uses a binary search.  |
683 `------------------------------------------------------------*/
684
685 static int
686 search_table (WORD *word, WORD_TABLE *table)
687 {
688   int lowest;                   /* current lowest possible index */
689   int highest;                  /* current highest possible index */
690   int middle;                   /* current middle index */
691   int value;                    /* value from last comparison */
692
693   lowest = 0;
694   highest = table->length - 1;
695   while (lowest <= highest)
696     {
697       middle = (lowest + highest) / 2;
698       value = compare_words (word, table->start + middle);
699       if (value < 0)
700         highest = middle - 1;
701       else if (value > 0)
702         lowest = middle + 1;
703       else
704         return 1;
705     }
706   return 0;
707 }
708
709 /*---------------------------------------------------------------------.
710 | Sort the whole occurs table in memory.  Presumably, `qsort' does not |
711 | take intermediate copies or table elements, so the sort will be      |
712 | stabilized throughout the comparison routine.                        |
713 `---------------------------------------------------------------------*/
714
715 static void
716 sort_found_occurs (void)
717 {
718
719   /* Only one language for the time being.  */
720
721   qsort (occurs_table[0], number_of_occurs[0], sizeof (OCCURS),
722          compare_occurs);
723 }
724 \f
725 /* Parameter files reading routines.  */
726
727 /*----------------------------------------------------------------------.
728 | Read a file named FILE_NAME, containing a set of break characters.    |
729 | Build a content to the array word_fastmap in which all characters are |
730 | allowed except those found in the file.  Characters may be repeated.  |
731 `----------------------------------------------------------------------*/
732
733 static void
734 digest_break_file (const char *file_name)
735 {
736   BLOCK file_contents;          /* to receive a copy of the file */
737   char *cursor;                 /* cursor in file copy */
738
739   swallow_file_in_memory (file_name, &file_contents);
740
741   /* Make the fastmap and record the file contents in it.  */
742
743   memset (word_fastmap, 1, CHAR_SET_SIZE);
744   for (cursor = file_contents.start; cursor < file_contents.end; cursor++)
745     word_fastmap[(unsigned char) *cursor] = 0;
746
747   if (!gnu_extensions)
748     {
749
750       /* If GNU extensions are enabled, the only way to avoid newline as
751          a break character is to write all the break characters in the
752          file with no newline at all, not even at the end of the file.
753          If disabled, spaces, tabs and newlines are always considered as
754          break characters even if not included in the break file.  */
755
756       word_fastmap[' '] = 0;
757       word_fastmap['\t'] = 0;
758       word_fastmap['\n'] = 0;
759     }
760
761   /* Return the space of the file, which is no more required.  */
762
763   free (file_contents.start);
764 }
765
766 /*-----------------------------------------------------------------------.
767 | Read a file named FILE_NAME, containing one word per line, then        |
768 | construct in TABLE a table of WORD descriptors for them.  The routine  |
769 | swallows the whole file in memory; this is at the expense of space     |
770 | needed for newlines, which are useless; however, the reading is fast.  |
771 `-----------------------------------------------------------------------*/
772
773 static void
774 digest_word_file (const char *file_name, WORD_TABLE *table)
775 {
776   BLOCK file_contents;          /* to receive a copy of the file */
777   char *cursor;                 /* cursor in file copy */
778   char *word_start;             /* start of the current word */
779
780   swallow_file_in_memory (file_name, &file_contents);
781
782   table->start = NULL;
783   table->length = 0;
784
785   /* Read the whole file.  */
786
787   cursor = file_contents.start;
788   while (cursor < file_contents.end)
789     {
790
791       /* Read one line, and save the word in contains.  */
792
793       word_start = cursor;
794       while (cursor < file_contents.end && *cursor != '\n')
795         cursor++;
796
797       /* Record the word in table if it is not empty.  */
798
799       if (cursor > word_start)
800         {
801           ALLOC_NEW_WORD (table);
802           table->start[table->length].start = word_start;
803           table->start[table->length].size = cursor - word_start;
804           table->length++;
805         }
806
807       /* This test allows for an incomplete line at end of file.  */
808
809       if (cursor < file_contents.end)
810         cursor++;
811     }
812
813   /* Finally, sort all the words read.  */
814
815   qsort (table->start, table->length, (size_t) sizeof (WORD), compare_words);
816 }
817 \f
818 /* Keyword recognition and selection.  */
819
820 /*----------------------------------------------------------------------.
821 | For each keyword in the source text, constructs an OCCURS structure.  |
822 `----------------------------------------------------------------------*/
823
824 static void
825 find_occurs_in_text (void)
826 {
827   char *cursor;                 /* for scanning the source text */
828   char *scan;                   /* for scanning the source text also */
829   char *line_start;             /* start of the current input line */
830   char *line_scan;              /* newlines scanned until this point */
831   int reference_length;         /* length of reference in input mode */
832   WORD possible_key;            /* possible key, to ease searches */
833   OCCURS *occurs_cursor;        /* current OCCURS under construction */
834
835   char *context_start;          /* start of left context */
836   char *context_end;            /* end of right context */
837   char *word_start;             /* start of word */
838   char *word_end;               /* end of word */
839   char *next_context_start;     /* next start of left context */
840
841   /* reference_length is always used within `if (input_reference)'.
842      However, GNU C diagnoses that it may be used uninitialized.  The
843      following assignment is merely to shut it up.  */
844
845   reference_length = 0;
846
847   /* Tracking where lines start is helpful for reference processing.  In
848      auto reference mode, this allows counting lines.  In input reference
849      mode, this permits finding the beginning of the references.
850
851      The first line begins with the file, skip immediately this very first
852      reference in input reference mode, to help further rejection any word
853      found inside it.  Also, unconditionally assigning these variable has
854      the happy effect of shutting up lint.  */
855
856   line_start = text_buffer.start;
857   line_scan = line_start;
858   if (input_reference)
859     {
860       SKIP_NON_WHITE (line_scan, text_buffer.end);
861       reference_length = line_scan - line_start;
862       SKIP_WHITE (line_scan, text_buffer.end);
863     }
864
865   /* Process the whole buffer, one line or one sentence at a time.  */
866
867   for (cursor = text_buffer.start;
868        cursor < text_buffer.end;
869        cursor = next_context_start)
870     {
871
872       /* `context_start' gets initialized before the processing of each
873          line, or once for the whole buffer if no end of line or sentence
874          sequence separator.  */
875
876       context_start = cursor;
877
878       /* If a end of line or end of sentence sequence is defined and
879          non-empty, `next_context_start' will be recomputed to be the end of
880          each line or sentence, before each one is processed.  If no such
881          sequence, then `next_context_start' is set at the end of the whole
882          buffer, which is then considered to be a single line or sentence.
883          This test also accounts for the case of an incomplete line or
884          sentence at the end of the buffer.  */
885
886       if (context_regex_string
887           && (re_search (context_regex, cursor, text_buffer.end - cursor,
888                          0, text_buffer.end - cursor, &context_regs)
889               >= 0))
890         next_context_start = cursor + context_regs.end[0];
891
892       else
893         next_context_start = text_buffer.end;
894
895       /* Include the separator into the right context, but not any suffix
896          white space in this separator; this insures it will be seen in
897          output and will not take more space than necessary.  */
898
899       context_end = next_context_start;
900       SKIP_WHITE_BACKWARDS (context_end, context_start);
901
902       /* Read and process a single input line or sentence, one word at a
903          time.  */
904
905       while (1)
906         {
907           if (word_regex)
908
909             /* If a word regexp has been compiled, use it to skip at the
910                beginning of the next word.  If there is no such word, exit
911                the loop.  */
912
913             {
914               if (re_search (word_regex, cursor, context_end - cursor,
915                              0, context_end - cursor, &word_regs)
916                   < 0)
917                 break;
918               word_start = cursor + word_regs.start[0];
919               word_end = cursor + word_regs.end[0];
920             }
921           else
922
923             /* Avoid re_search and use the fastmap to skip to the
924                beginning of the next word.  If there is no more word in
925                the buffer, exit the loop.  */
926
927             {
928               scan = cursor;
929               while (scan < context_end
930                      && !word_fastmap[(unsigned char) *scan])
931                 scan++;
932
933               if (scan == context_end)
934                 break;
935
936               word_start = scan;
937
938               while (scan < context_end
939                      && word_fastmap[(unsigned char) *scan])
940                 scan++;
941
942               word_end = scan;
943             }
944
945           /* Skip right to the beginning of the found word.  */
946
947           cursor = word_start;
948
949           /* Skip any zero length word.  Just advance a single position,
950              then go fetch the next word.  */
951
952           if (word_end == word_start)
953             {
954               cursor++;
955               continue;
956             }
957
958           /* This is a genuine, non empty word, so save it as a possible
959              key.  Then skip over it.  Also, maintain the maximum length of
960              all words read so far.  It is mandatory to take the maximum
961              length of all words in the file, without considering if they
962              are actually kept or rejected, because backward jumps at output
963              generation time may fall in *any* word.  */
964
965           possible_key.start = cursor;
966           possible_key.size = word_end - word_start;
967           cursor += possible_key.size;
968
969           if (possible_key.size > maximum_word_length)
970             maximum_word_length = possible_key.size;
971
972           /* In input reference mode, update `line_start' from its previous
973              value.  Count the lines just in case auto reference mode is
974              also selected. If it happens that the word just matched is
975              indeed part of a reference; just ignore it.  */
976
977           if (input_reference)
978             {
979               while (line_scan < possible_key.start)
980                 if (*line_scan == '\n')
981                   {
982                     total_line_count++;
983                     line_scan++;
984                     line_start = line_scan;
985                     SKIP_NON_WHITE (line_scan, text_buffer.end);
986                     reference_length = line_scan - line_start;
987                   }
988                 else
989                   line_scan++;
990               if (line_scan > possible_key.start)
991                 continue;
992             }
993
994           /* Ignore the word if an `Ignore words' table exists and if it is
995              part of it.  Also ignore the word if an `Only words' table and
996              if it is *not* part of it.
997
998              It is allowed that both tables be used at once, even if this
999              may look strange for now.  Just ignore a word that would appear
1000              in both.  If regexps are eventually implemented for these
1001              tables, the Ignore table could then reject words that would
1002              have been previously accepted by the Only table.  */
1003
1004           if (ignore_file && search_table (&possible_key, &ignore_table))
1005             continue;
1006           if (only_file && !search_table (&possible_key, &only_table))
1007             continue;
1008
1009           /* A non-empty word has been found.  First of all, insure
1010              proper allocation of the next OCCURS, and make a pointer to
1011              where it will be constructed.  */
1012
1013           ALLOC_NEW_OCCURS (0);
1014           occurs_cursor = occurs_table[0] + number_of_occurs[0];
1015
1016           /* Define the refence field, if any.  */
1017
1018           if (auto_reference)
1019             {
1020
1021               /* While auto referencing, update `line_start' from its
1022                  previous value, counting lines as we go.  If input
1023                  referencing at the same time, `line_start' has been
1024                  advanced earlier, and the following loop is never really
1025                  executed.  */
1026
1027               while (line_scan < possible_key.start)
1028                 if (*line_scan == '\n')
1029                   {
1030                     total_line_count++;
1031                     line_scan++;
1032                     line_start = line_scan;
1033                     SKIP_NON_WHITE (line_scan, text_buffer.end);
1034                   }
1035                 else
1036                   line_scan++;
1037
1038               occurs_cursor->reference = total_line_count;
1039             }
1040           else if (input_reference)
1041             {
1042
1043               /* If only input referencing, `line_start' has been computed
1044                  earlier to detect the case the word matched would be part
1045                  of the reference.  The reference position is simply the
1046                  value of `line_start'.  */
1047
1048               occurs_cursor->reference
1049                 = (DELTA) (line_start - possible_key.start);
1050               if (reference_length > reference_max_width)
1051                 reference_max_width = reference_length;
1052             }
1053
1054           /* Exclude the reference from the context in simple cases.  */
1055
1056           if (input_reference && line_start == context_start)
1057             {
1058               SKIP_NON_WHITE (context_start, context_end);
1059               SKIP_WHITE (context_start, context_end);
1060             }
1061
1062           /* Completes the OCCURS structure.  */
1063
1064           occurs_cursor->key = possible_key;
1065           occurs_cursor->left = context_start - possible_key.start;
1066           occurs_cursor->right = context_end - possible_key.start;
1067
1068           number_of_occurs[0]++;
1069         }
1070     }
1071 }
1072 \f
1073 /* Formatting and actual output - service routines.  */
1074
1075 /*-----------------------------------------.
1076 | Prints some NUMBER of spaces on stdout.  |
1077 `-----------------------------------------*/
1078
1079 static void
1080 print_spaces (int number)
1081 {
1082   int counter;
1083
1084   for (counter = number; counter > 0; counter--)
1085     putchar (' ');
1086 }
1087
1088 /*-------------------------------------.
1089 | Prints the field provided by FIELD.  |
1090 `-------------------------------------*/
1091
1092 static void
1093 print_field (BLOCK field)
1094 {
1095   char *cursor;                 /* Cursor in field to print */
1096   int character;                /* Current character */
1097   int base;                     /* Base character, without diacritic */
1098   int diacritic;                /* Diacritic code for the character */
1099
1100   /* Whitespace is not really compressed.  Instead, each white space
1101      character (tab, vt, ht etc.) is printed as one single space.  */
1102
1103   for (cursor = field.start; cursor < field.end; cursor++)
1104     {
1105       character = (unsigned char) *cursor;
1106       if (edited_flag[character])
1107         {
1108
1109           /* First check if this is a diacriticized character.
1110
1111              This works only for TeX.  I do not know how diacriticized
1112              letters work with `roff'.  Please someone explain it to me!  */
1113
1114           diacritic = todiac (character);
1115           if (diacritic != 0 && output_format == TEX_FORMAT)
1116             {
1117               base = tobase (character);
1118               switch (diacritic)
1119                 {
1120
1121                 case 1:         /* Latin diphthongs */
1122                   switch (base)
1123                     {
1124                     case 'o':
1125                       fputs ("\\oe{}", stdout);
1126                       break;
1127
1128                     case 'O':
1129                       fputs ("\\OE{}", stdout);
1130                       break;
1131
1132                     case 'a':
1133                       fputs ("\\ae{}", stdout);
1134                       break;
1135
1136                     case 'A':
1137                       fputs ("\\AE{}", stdout);
1138                       break;
1139
1140                     default:
1141                       putchar (' ');
1142                     }
1143                   break;
1144
1145                 case 2:         /* Acute accent */
1146                   printf ("\\'%s%c", (base == 'i' ? "\\" : ""), base);
1147                   break;
1148
1149                 case 3:         /* Grave accent */
1150                   printf ("\\`%s%c", (base == 'i' ? "\\" : ""), base);
1151                   break;
1152
1153                 case 4:         /* Circumflex accent */
1154                   printf ("\\^%s%c", (base == 'i' ? "\\" : ""), base);
1155                   break;
1156
1157                 case 5:         /* Diaeresis */
1158                   printf ("\\\"%s%c", (base == 'i' ? "\\" : ""), base);
1159                   break;
1160
1161                 case 6:         /* Tilde accent */
1162                   printf ("\\~%s%c", (base == 'i' ? "\\" : ""), base);
1163                   break;
1164
1165                 case 7:         /* Cedilla */
1166                   printf ("\\c{%c}", base);
1167                   break;
1168
1169                 case 8:         /* Small circle beneath */
1170                   switch (base)
1171                     {
1172                     case 'a':
1173                       fputs ("\\aa{}", stdout);
1174                       break;
1175
1176                     case 'A':
1177                       fputs ("\\AA{}", stdout);
1178                       break;
1179
1180                     default:
1181                       putchar (' ');
1182                     }
1183                   break;
1184
1185                 case 9:         /* Strike through */
1186                   switch (base)
1187                     {
1188                     case 'o':
1189                       fputs ("\\o{}", stdout);
1190                       break;
1191
1192                     case 'O':
1193                       fputs ("\\O{}", stdout);
1194                       break;
1195
1196                     default:
1197                       putchar (' ');
1198                     }
1199                   break;
1200                 }
1201             }
1202           else
1203
1204             /* This is not a diacritic character, so handle cases which are
1205                really specific to `roff' or TeX.  All white space processing
1206                is done as the default case of this switch.  */
1207
1208             switch (character)
1209               {
1210               case '"':
1211                 /* In roff output format, double any quote.  */
1212                 putchar ('"');
1213                 putchar ('"');
1214                 break;
1215
1216               case '$':
1217               case '%':
1218               case '&':
1219               case '#':
1220               case '_':
1221                 /* In TeX output format, precede these with a backslash.  */
1222                 putchar ('\\');
1223                 putchar (character);
1224                 break;
1225
1226               case '{':
1227               case '}':
1228                 /* In TeX output format, precede these with a backslash and
1229                    force mathematical mode.  */
1230                 printf ("$\\%c$", character);
1231                 break;
1232
1233               case '\\':
1234                 /* In TeX output mode, request production of a backslash.  */
1235                 fputs ("\\backslash{}", stdout);
1236                 break;
1237
1238               default:
1239                 /* Any other flagged character produces a single space.  */
1240                 putchar (' ');
1241               }
1242         }
1243       else
1244         putchar (*cursor);
1245     }
1246 }
1247 \f
1248 /* Formatting and actual output - planning routines.  */
1249
1250 /*--------------------------------------------------------------------.
1251 | From information collected from command line options and input file |
1252 | readings, compute and fix some output parameter values.             |
1253 `--------------------------------------------------------------------*/
1254
1255 static void
1256 fix_output_parameters (void)
1257 {
1258   int file_index;               /* index in text input file arrays */
1259   int line_ordinal;             /* line ordinal value for reference */
1260   char ordinal_string[12];      /* edited line ordinal for reference */
1261   int reference_width;          /* width for the whole reference */
1262   int character;                /* character ordinal */
1263   const char *cursor;           /* cursor in some constant strings */
1264
1265   /* In auto reference mode, the maximum width of this field is
1266      precomputed and subtracted from the overall line width.  Add one for
1267      the column which separate the file name from the line number.  */
1268
1269   if (auto_reference)
1270     {
1271       reference_max_width = 0;
1272       for (file_index = 0; file_index < number_input_files; file_index++)
1273         {
1274           line_ordinal = file_line_count[file_index] + 1;
1275           if (file_index > 0)
1276             line_ordinal -= file_line_count[file_index - 1];
1277           sprintf (ordinal_string, "%d", line_ordinal);
1278           reference_width = strlen (ordinal_string);
1279           if (input_file_name[file_index])
1280             reference_width += strlen (input_file_name[file_index]);
1281           if (reference_width > reference_max_width)
1282             reference_max_width = reference_width;
1283         }
1284       reference_max_width++;
1285       reference.start = (char *) xmalloc ((size_t) reference_max_width + 1);
1286     }
1287
1288   /* If the reference appears to the left of the output line, reserve some
1289      space for it right away, including one gap size.  */
1290
1291   if ((auto_reference || input_reference) && !right_reference)
1292     line_width -= reference_max_width + gap_size;
1293
1294   /* The output lines, minimally, will contain from left to right a left
1295      context, a gap, and a keyword followed by the right context with no
1296      special intervening gap.  Half of the line width is dedicated to the
1297      left context and the gap, the other half is dedicated to the keyword
1298      and the right context; these values are computed once and for all here.
1299      There also are tail and head wrap around fields, used when the keyword
1300      is near the beginning or the end of the line, or when some long word
1301      cannot fit in, but leave place from wrapped around shorter words.  The
1302      maximum width of these fields are recomputed separately for each line,
1303      on a case by case basis.  It is worth noting that it cannot happen that
1304      both the tail and head fields are used at once.  */
1305
1306   half_line_width = line_width / 2;
1307   before_max_width = half_line_width - gap_size;
1308   keyafter_max_width = half_line_width;
1309
1310   /* If truncation_string is the empty string, make it NULL to speed up
1311      tests.  In this case, truncation_string_length will never get used, so
1312      there is no need to set it.  */
1313
1314   if (truncation_string && *truncation_string)
1315     truncation_string_length = strlen (truncation_string);
1316   else
1317     truncation_string = NULL;
1318
1319   if (gnu_extensions)
1320     {
1321
1322       /* When flagging truncation at the left of the keyword, the
1323          truncation mark goes at the beginning of the before field,
1324          unless there is a head field, in which case the mark goes at the
1325          left of the head field.  When flagging truncation at the right
1326          of the keyword, the mark goes at the end of the keyafter field,
1327          unless there is a tail field, in which case the mark goes at the
1328          end of the tail field.  Only eight combination cases could arise
1329          for truncation marks:
1330
1331          . None.
1332          . One beginning the before field.
1333          . One beginning the head field.
1334          . One ending the keyafter field.
1335          . One ending the tail field.
1336          . One beginning the before field, another ending the keyafter field.
1337          . One ending the tail field, another beginning the before field.
1338          . One ending the keyafter field, another beginning the head field.
1339
1340          So, there is at most two truncation marks, which could appear both
1341          on the left side of the center of the output line, both on the
1342          right side, or one on either side.  */
1343
1344       before_max_width -= 2 * truncation_string_length;
1345       keyafter_max_width -= 2 * truncation_string_length;
1346     }
1347   else
1348     {
1349
1350       /* I never figured out exactly how UNIX' ptx plans the output width
1351          of its various fields.  If GNU extensions are disabled, do not
1352          try computing the field widths correctly; instead, use the
1353          following formula, which does not completely imitate UNIX' ptx,
1354          but almost.  */
1355
1356       keyafter_max_width -= 2 * truncation_string_length + 1;
1357     }
1358
1359   /* Compute which characters need special output processing.  Initialize
1360      by flagging any white space character.  Some systems do not consider
1361      form feed as a space character, but we do.  */
1362
1363   for (character = 0; character < CHAR_SET_SIZE; character++)
1364     edited_flag[character] = isspace (character) != 0;
1365   edited_flag['\f'] = 1;
1366
1367   /* Complete the special character flagging according to selected output
1368      format.  */
1369
1370   switch (output_format)
1371     {
1372     case UNKNOWN_FORMAT:
1373       /* Should never happen.  */
1374
1375     case DUMB_FORMAT:
1376       break;
1377
1378     case ROFF_FORMAT:
1379
1380       /* `Quote' characters should be doubled.  */
1381
1382       edited_flag['"'] = 1;
1383       break;
1384
1385     case TEX_FORMAT:
1386
1387       /* Various characters need special processing.  */
1388
1389       for (cursor = "$%&#_{}\\"; *cursor; cursor++)
1390         edited_flag[(unsigned char) *cursor] = 1;
1391
1392       /* Any character with 8th bit set will print to a single space, unless
1393          it is diacriticized.  */
1394
1395       for (character = 0200; character < CHAR_SET_SIZE; character++)
1396         edited_flag[character] = todiac (character) != 0;
1397       break;
1398     }
1399 }
1400
1401 /*------------------------------------------------------------------.
1402 | Compute the position and length of all the output fields, given a |
1403 | pointer to some OCCURS.                                           |
1404 `------------------------------------------------------------------*/
1405
1406 static void
1407 define_all_fields (OCCURS *occurs)
1408 {
1409   int tail_max_width;           /* allowable width of tail field */
1410   int head_max_width;           /* allowable width of head field */
1411   char *cursor;                 /* running cursor in source text */
1412   char *left_context_start;     /* start of left context */
1413   char *right_context_end;      /* end of right context */
1414   char *left_field_start;       /* conservative start for `head'/`before' */
1415   int file_index;               /* index in text input file arrays */
1416   const char *file_name;        /* file name for reference */
1417   int line_ordinal;             /* line ordinal for reference */
1418
1419   /* Define `keyafter', start of left context and end of right context.
1420      `keyafter' starts at the saved position for keyword and extend to the
1421      right from the end of the keyword, eating separators or full words, but
1422      not beyond maximum allowed width for `keyafter' field or limit for the
1423      right context.  Suffix spaces will be removed afterwards.  */
1424
1425   keyafter.start = occurs->key.start;
1426   keyafter.end = keyafter.start + occurs->key.size;
1427   left_context_start = keyafter.start + occurs->left;
1428   right_context_end = keyafter.start + occurs->right;
1429
1430   cursor = keyafter.end;
1431   while (cursor < right_context_end
1432          && cursor <= keyafter.start + keyafter_max_width)
1433     {
1434       keyafter.end = cursor;
1435       SKIP_SOMETHING (cursor, right_context_end);
1436     }
1437   if (cursor <= keyafter.start + keyafter_max_width)
1438     keyafter.end = cursor;
1439
1440   keyafter_truncation = truncation_string && keyafter.end < right_context_end;
1441
1442   SKIP_WHITE_BACKWARDS (keyafter.end, keyafter.start);
1443
1444   /* When the left context is wide, it might take some time to catch up from
1445      the left context boundary to the beginning of the `head' or `before'
1446      fields.  So, in this case, to speed the catchup, we jump back from the
1447      keyword, using some secure distance, possibly falling in the middle of
1448      a word.  A secure backward jump would be at least half the maximum
1449      width of a line, plus the size of the longest word met in the whole
1450      input.  We conclude this backward jump by a skip forward of at least
1451      one word.  In this manner, we should not inadvertently accept only part
1452      of a word.  From the reached point, when it will be time to fix the
1453      beginning of `head' or `before' fields, we will skip forward words or
1454      delimiters until we get sufficiently near.  */
1455
1456   if (-occurs->left > half_line_width + maximum_word_length)
1457     {
1458       left_field_start
1459         = keyafter.start - (half_line_width + maximum_word_length);
1460       SKIP_SOMETHING (left_field_start, keyafter.start);
1461     }
1462   else
1463     left_field_start = keyafter.start + occurs->left;
1464
1465   /* `before' certainly ends at the keyword, but not including separating
1466      spaces.  It starts after than the saved value for the left context, by
1467      advancing it until it falls inside the maximum allowed width for the
1468      before field.  There will be no prefix spaces either.  `before' only
1469      advances by skipping single separators or whole words. */
1470
1471   before.start = left_field_start;
1472   before.end = keyafter.start;
1473   SKIP_WHITE_BACKWARDS (before.end, before.start);
1474
1475   while (before.start + before_max_width < before.end)
1476     SKIP_SOMETHING (before.start, before.end);
1477
1478   if (truncation_string)
1479     {
1480       cursor = before.start;
1481       SKIP_WHITE_BACKWARDS (cursor, text_buffer.start);
1482       before_truncation = cursor > left_context_start;
1483     }
1484   else
1485     before_truncation = 0;
1486
1487   SKIP_WHITE (before.start, text_buffer.end);
1488
1489   /* The tail could not take more columns than what has been left in the
1490      left context field, and a gap is mandatory.  It starts after the
1491      right context, and does not contain prefixed spaces.  It ends at
1492      the end of line, the end of buffer or when the tail field is full,
1493      whichever comes first.  It cannot contain only part of a word, and
1494      has no suffixed spaces.  */
1495
1496   tail_max_width
1497     = before_max_width - (before.end - before.start) - gap_size;
1498
1499   if (tail_max_width > 0)
1500     {
1501       tail.start = keyafter.end;
1502       SKIP_WHITE (tail.start, text_buffer.end);
1503
1504       tail.end = tail.start;
1505       cursor = tail.end;
1506       while (cursor < right_context_end
1507              && cursor < tail.start + tail_max_width)
1508         {
1509           tail.end = cursor;
1510           SKIP_SOMETHING (cursor, right_context_end);
1511         }
1512
1513       if (cursor < tail.start + tail_max_width)
1514         tail.end = cursor;
1515
1516       if (tail.end > tail.start)
1517         {
1518           keyafter_truncation = 0;
1519           tail_truncation = truncation_string && tail.end < right_context_end;
1520         }
1521       else
1522         tail_truncation = 0;
1523
1524       SKIP_WHITE_BACKWARDS (tail.end, tail.start);
1525     }
1526   else
1527     {
1528
1529       /* No place left for a tail field.  */
1530
1531       tail.start = NULL;
1532       tail.end = NULL;
1533       tail_truncation = 0;
1534     }
1535
1536   /* `head' could not take more columns than what has been left in the right
1537      context field, and a gap is mandatory.  It ends before the left
1538      context, and does not contain suffixed spaces.  Its pointer is advanced
1539      until the head field has shrunk to its allowed width.  It cannot
1540      contain only part of a word, and has no suffixed spaces.  */
1541
1542   head_max_width
1543     = keyafter_max_width - (keyafter.end - keyafter.start) - gap_size;
1544
1545   if (head_max_width > 0)
1546     {
1547       head.end = before.start;
1548       SKIP_WHITE_BACKWARDS (head.end, text_buffer.start);
1549
1550       head.start = left_field_start;
1551       while (head.start + head_max_width < head.end)
1552         SKIP_SOMETHING (head.start, head.end);
1553
1554       if (head.end > head.start)
1555         {
1556           before_truncation = 0;
1557           head_truncation = (truncation_string
1558                              && head.start > left_context_start);
1559         }
1560       else
1561         head_truncation = 0;
1562
1563       SKIP_WHITE (head.start, head.end);
1564     }
1565   else
1566     {
1567
1568       /* No place left for a head field.  */
1569
1570       head.start = NULL;
1571       head.end = NULL;
1572       head_truncation = 0;
1573     }
1574
1575   if (auto_reference)
1576     {
1577
1578       /* Construct the reference text in preallocated space from the file
1579          name and the line number.  Find out in which file the reference
1580          occurred.  Standard input yields an empty file name.  Insure line
1581          numbers are one based, even if they are computed zero based.  */
1582
1583       file_index = 0;
1584       while (file_line_count[file_index] < occurs->reference)
1585         file_index++;
1586
1587       file_name = input_file_name[file_index];
1588       if (!file_name)
1589         file_name = "";
1590
1591       line_ordinal = occurs->reference + 1;
1592       if (file_index > 0)
1593         line_ordinal -= file_line_count[file_index - 1];
1594
1595       sprintf (reference.start, "%s:%d", file_name, line_ordinal);
1596       reference.end = reference.start + strlen (reference.start);
1597     }
1598   else if (input_reference)
1599     {
1600
1601       /* Reference starts at saved position for reference and extends right
1602          until some white space is met.  */
1603
1604       reference.start = keyafter.start + (DELTA) occurs->reference;
1605       reference.end = reference.start;
1606       SKIP_NON_WHITE (reference.end, right_context_end);
1607     }
1608 }
1609 \f
1610 /* Formatting and actual output - control routines.  */
1611
1612 /*----------------------------------------------------------------------.
1613 | Output the current output fields as one line for `troff' or `nroff'.  |
1614 `----------------------------------------------------------------------*/
1615
1616 static void
1617 output_one_roff_line (void)
1618 {
1619   /* Output the `tail' field.  */
1620
1621   printf (".%s \"", macro_name);
1622   print_field (tail);
1623   if (tail_truncation)
1624     fputs (truncation_string, stdout);
1625   putchar ('"');
1626
1627   /* Output the `before' field.  */
1628
1629   fputs (" \"", stdout);
1630   if (before_truncation)
1631     fputs (truncation_string, stdout);
1632   print_field (before);
1633   putchar ('"');
1634
1635   /* Output the `keyafter' field.  */
1636
1637   fputs (" \"", stdout);
1638   print_field (keyafter);
1639   if (keyafter_truncation)
1640     fputs (truncation_string, stdout);
1641   putchar ('"');
1642
1643   /* Output the `head' field.  */
1644
1645   fputs (" \"", stdout);
1646   if (head_truncation)
1647     fputs (truncation_string, stdout);
1648   print_field (head);
1649   putchar ('"');
1650
1651   /* Conditionally output the `reference' field.  */
1652
1653   if (auto_reference || input_reference)
1654     {
1655       fputs (" \"", stdout);
1656       print_field (reference);
1657       putchar ('"');
1658     }
1659
1660   putchar ('\n');
1661 }
1662
1663 /*---------------------------------------------------------.
1664 | Output the current output fields as one line for `TeX'.  |
1665 `---------------------------------------------------------*/
1666
1667 static void
1668 output_one_tex_line (void)
1669 {
1670   BLOCK key;                    /* key field, isolated */
1671   BLOCK after;                  /* after field, isolated */
1672   char *cursor;                 /* running cursor in source text */
1673
1674   printf ("\\%s ", macro_name);
1675   fputs ("{", stdout);
1676   print_field (tail);
1677   fputs ("}{", stdout);
1678   print_field (before);
1679   fputs ("}{", stdout);
1680   key.start = keyafter.start;
1681   after.end = keyafter.end;
1682   cursor = keyafter.start;
1683   SKIP_SOMETHING (cursor, keyafter.end);
1684   key.end = cursor;
1685   after.start = cursor;
1686   print_field (key);
1687   fputs ("}{", stdout);
1688   print_field (after);
1689   fputs ("}{", stdout);
1690   print_field (head);
1691   fputs ("}", stdout);
1692   if (auto_reference || input_reference)
1693     {
1694       fputs ("{", stdout);
1695       print_field (reference);
1696       fputs ("}", stdout);
1697     }
1698   fputs ("\n", stdout);
1699 }
1700
1701 /*-------------------------------------------------------------------.
1702 | Output the current output fields as one line for a dumb terminal.  |
1703 `-------------------------------------------------------------------*/
1704
1705 static void
1706 output_one_dumb_line (void)
1707 {
1708   if (!right_reference)
1709     {
1710       if (auto_reference)
1711         {
1712
1713           /* Output the `reference' field, in such a way that GNU emacs
1714              next-error will handle it.  The ending colon is taken from the
1715              gap which follows.  */
1716
1717           print_field (reference);
1718           putchar (':');
1719           print_spaces (reference_max_width
1720                         + gap_size
1721                         - (reference.end - reference.start)
1722                         - 1);
1723         }
1724       else
1725         {
1726
1727           /* Output the `reference' field and its following gap.  */
1728
1729           print_field (reference);
1730           print_spaces (reference_max_width
1731                         + gap_size
1732                         - (reference.end - reference.start));
1733         }
1734     }
1735
1736   if (tail.start < tail.end)
1737     {
1738       /* Output the `tail' field.  */
1739
1740       print_field (tail);
1741       if (tail_truncation)
1742         fputs (truncation_string, stdout);
1743
1744       print_spaces (half_line_width - gap_size
1745                     - (before.end - before.start)
1746                     - (before_truncation ? truncation_string_length : 0)
1747                     - (tail.end - tail.start)
1748                     - (tail_truncation ? truncation_string_length : 0));
1749     }
1750   else
1751     print_spaces (half_line_width - gap_size
1752                   - (before.end - before.start)
1753                   - (before_truncation ? truncation_string_length : 0));
1754
1755   /* Output the `before' field.  */
1756
1757   if (before_truncation)
1758     fputs (truncation_string, stdout);
1759   print_field (before);
1760
1761   print_spaces (gap_size);
1762
1763   /* Output the `keyafter' field.  */
1764
1765   print_field (keyafter);
1766   if (keyafter_truncation)
1767     fputs (truncation_string, stdout);
1768
1769   if (head.start < head.end)
1770     {
1771       /* Output the `head' field.  */
1772
1773       print_spaces (half_line_width
1774                     - (keyafter.end - keyafter.start)
1775                     - (keyafter_truncation ? truncation_string_length : 0)
1776                     - (head.end - head.start)
1777                     - (head_truncation ? truncation_string_length : 0));
1778       if (head_truncation)
1779         fputs (truncation_string, stdout);
1780       print_field (head);
1781     }
1782   else
1783
1784     if ((auto_reference || input_reference) && right_reference)
1785       print_spaces (half_line_width
1786                     - (keyafter.end - keyafter.start)
1787                     - (keyafter_truncation ? truncation_string_length : 0));
1788
1789   if ((auto_reference || input_reference) && right_reference)
1790     {
1791       /* Output the `reference' field.  */
1792
1793       print_spaces (gap_size);
1794       print_field (reference);
1795     }
1796
1797   fputs ("\n", stdout);
1798 }
1799
1800 /*------------------------------------------------------------------------.
1801 | Scan the whole occurs table and, for each entry, output one line in the |
1802 | appropriate format.                                                     |
1803 `------------------------------------------------------------------------*/
1804
1805 static void
1806 generate_all_output (void)
1807 {
1808   int occurs_index;             /* index of keyword entry being processed */
1809   OCCURS *occurs_cursor;        /* current keyword entry being processed */
1810
1811   /* The following assignments are useful to provide default values in case
1812      line contexts or references are not used, in which case these variables
1813      would never be computed.  */
1814
1815   tail.start = NULL;
1816   tail.end = NULL;
1817   tail_truncation = 0;
1818
1819   head.start = NULL;
1820   head.end = NULL;
1821   head_truncation = 0;
1822
1823   /* Loop over all keyword occurrences.  */
1824
1825   occurs_cursor = occurs_table[0];
1826
1827   for (occurs_index = 0; occurs_index < number_of_occurs[0]; occurs_index++)
1828     {
1829       /* Compute the exact size of every field and whenever truncation flags
1830          are present or not.  */
1831
1832       define_all_fields (occurs_cursor);
1833
1834       /* Produce one output line according to selected format.  */
1835
1836       switch (output_format)
1837         {
1838         case UNKNOWN_FORMAT:
1839           /* Should never happen.  */
1840
1841         case DUMB_FORMAT:
1842           output_one_dumb_line ();
1843           break;
1844
1845         case ROFF_FORMAT:
1846           output_one_roff_line ();
1847           break;
1848
1849         case TEX_FORMAT:
1850           output_one_tex_line ();
1851           break;
1852         }
1853
1854       /* Advance the cursor into the occurs table.  */
1855
1856       occurs_cursor++;
1857     }
1858 }
1859 \f
1860 /* Option decoding and main program.  */
1861
1862 /*------------------------------------------------------.
1863 | Print program identification and options, then exit.  |
1864 `------------------------------------------------------*/
1865
1866 void
1867 usage (int status)
1868 {
1869   if (status != EXIT_SUCCESS)
1870     fprintf (stderr, _("Try `%s --help' for more information.\n"),
1871              program_name);
1872   else
1873     {
1874       printf (_("\
1875 Usage: %s [OPTION]... [INPUT]...   (without -G)\n\
1876   or:  %s -G [OPTION]... [INPUT [OUTPUT]]\n"),
1877               program_name, program_name);
1878       fputs (_("\
1879 Mandatory arguments to long options are mandatory for short options too.\n\
1880 \n\
1881   -A, --auto-reference           output automatically generated references\n\
1882   -C, --copyright                display Copyright and copying conditions\n\
1883   -G, --traditional              behave more like System V `ptx'\n\
1884   -F, --flag-truncation=STRING   use STRING for flagging line truncations\n\
1885   -M, --macro-name=STRING        macro name to use instead of `xx'\n\
1886   -O, --format=roff              generate output as roff directives\n\
1887   -R, --right-side-refs          put references at right, not counted in -w\n\
1888   -S, --sentence-regexp=REGEXP   for end of lines or end of sentences\n\
1889   -T, --format=tex               generate output as TeX directives\n\
1890   -W, --word-regexp=REGEXP       use REGEXP to match each keyword\n\
1891   -b, --break-file=FILE          word break characters in this FILE\n\
1892   -f, --ignore-case              fold lower case to upper case for sorting\n\
1893   -g, --gap-size=NUMBER          gap size in columns between output fields\n\
1894   -i, --ignore-file=FILE         read ignore word list from FILE\n\
1895   -o, --only-file=FILE           read only word list from this FILE\n\
1896   -r, --references               first field of each line is a reference\n\
1897   -t, --typeset-mode               - not implemented -\n\
1898   -w, --width=NUMBER             output width in columns, reference excluded\n\
1899       --help                     display this help and exit\n\
1900       --version                  output version information and exit\n\
1901 \n\
1902 With no FILE or if FILE is -, read Standard Input.  `-F /' by default.\n"),
1903              stdout);
1904     }
1905   exit (status);
1906 }
1907
1908 /*----------------------------------------------------------------------.
1909 | Main program.  Decode ARGC arguments passed through the ARGV array of |
1910 | strings, then launch execution.                                       |
1911 `----------------------------------------------------------------------*/
1912
1913 /* Long options equivalences.  */
1914 static const struct option long_options[] =
1915 {
1916   {"auto-reference", no_argument, NULL, 'A'},
1917   {"break-file", required_argument, NULL, 'b'},
1918   {"copyright", no_argument, NULL, 'C'},
1919   {"flag-truncation", required_argument, NULL, 'F'},
1920   {"ignore-case", no_argument, NULL, 'f'},
1921   {"gap-size", required_argument, NULL, 'g'},
1922   {"help", no_argument, &show_help, 1},
1923   {"ignore-file", required_argument, NULL, 'i'},
1924   {"macro-name", required_argument, NULL, 'M'},
1925   {"only-file", required_argument, NULL, 'o'},
1926   {"references", no_argument, NULL, 'r'},
1927   {"right-side-refs", no_argument, NULL, 'R'},
1928   {"format", required_argument, NULL, 10},
1929   {"sentence-regexp", required_argument, NULL, 'S'},
1930   {"traditional", no_argument, NULL, 'G'},
1931   {"typeset-mode", no_argument, NULL, 't'},
1932   {"version", no_argument, &show_version, 1},
1933   {"width", required_argument, NULL, 'w'},
1934   {"word-regexp", required_argument, NULL, 'W'},
1935   {0, 0, 0, 0},
1936 };
1937
1938 static char const* const format_args[] =
1939 {
1940   "roff", "tex", 0
1941 };
1942
1943 static enum Format const format_vals[] =
1944 {
1945   ROFF_FORMAT, TEX_FORMAT
1946 };
1947
1948 int
1949 main (int argc, char *const argv[])
1950 {
1951   int optchar;                  /* argument character */
1952   int file_index;               /* index in text input file arrays */
1953
1954   /* Decode program options.  */
1955
1956   program_name = argv[0];
1957   setlocale (LC_ALL, "");
1958
1959 #if HAVE_SETCHRCLASS
1960   setchrclass (NULL);
1961 #endif
1962
1963   while (optchar = getopt_long (argc, argv, "ACF:GM:ORS:TW:b:i:fg:o:trw:",
1964                                 long_options, NULL),
1965          optchar != EOF)
1966     {
1967       switch (optchar)
1968         {
1969         default:
1970           usage (EXIT_FAILURE);
1971
1972         case 0:
1973           break;
1974
1975         case 'C':
1976           fputs (_("\
1977 This program is free software; you can redistribute it and/or modify\n\
1978 it under the terms of the GNU General Public License as published by\n\
1979 the Free Software Foundation; either version 2, or (at your option)\n\
1980 any later version.\n\
1981 \n\
1982 This program is distributed in the hope that it will be useful,\n\
1983 but WITHOUT ANY WARRANTY; without even the implied warranty of\n\
1984 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n\
1985 GNU General Public License for more details.\n\
1986 \n\
1987 You should have received a copy of the GNU General Public License\n\
1988 along with this program; if not, write to the Free Software Foundation,\n\
1989 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.\n"),
1990                  stdout);
1991
1992           exit (EXIT_SUCCESS);
1993
1994         case 'G':
1995           gnu_extensions = 0;
1996           break;
1997
1998         case 'b':
1999           break_file = optarg;
2000           break;
2001
2002         case 'f':
2003           ignore_case = 1;
2004           break;
2005
2006         case 'g':
2007           gap_size = atoi (optarg);
2008           break;
2009
2010         case 'i':
2011           ignore_file = optarg;
2012           break;
2013
2014         case 'o':
2015           only_file = optarg;
2016           break;
2017
2018         case 'r':
2019           input_reference = 1;
2020           break;
2021
2022         case 't':
2023           /* Yet to understand...  */
2024           break;
2025
2026         case 'w':
2027           line_width = atoi (optarg);
2028           break;
2029
2030         case 'A':
2031           auto_reference = 1;
2032           break;
2033
2034         case 'F':
2035           truncation_string = copy_unescaped_string (optarg);
2036           break;
2037
2038         case 'M':
2039           macro_name = optarg;
2040           break;
2041
2042         case 'O':
2043           output_format = ROFF_FORMAT;
2044           break;
2045
2046         case 'R':
2047           right_reference = 1;
2048           break;
2049
2050         case 'S':
2051           context_regex_string = copy_unescaped_string (optarg);
2052           break;
2053
2054         case 'T':
2055           output_format = TEX_FORMAT;
2056           break;
2057
2058         case 'W':
2059           word_regex_string = copy_unescaped_string (optarg);
2060           break;
2061
2062         case 10:
2063           output_format = XARGMATCH ("--format", optarg,
2064                                      format_args, format_vals);
2065         }
2066     }
2067
2068   /* Process trivial options.  */
2069
2070   if (show_help)
2071     usage (EXIT_SUCCESS);
2072
2073   if (show_version)
2074     {
2075       printf ("ptx (%s) %s\n", GNU_PACKAGE, VERSION);
2076       exit (EXIT_SUCCESS);
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, 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 }