*** empty log message ***
[coreutils.git] / src / ptx.c
blob75bc7ed65d9b44a04d0a9f7974e28d31bd372499
1 /* Permuted index for GNU, with keywords in their context.
2 Copyright (C) 1990, 1991, 1993, 1998-2000 Free Software Foundation, Inc.
3 François Pinard <pinard@iro.umontreal.ca>, 1988.
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.
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.
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.
19 François Pinard <pinard@iro.umontreal.ca> */
21 #include <config.h>
23 #include <stdio.h>
24 #include <getopt.h>
25 #include <sys/types.h>
26 #include "system.h"
27 #include "closeout.h"
28 #include "argmatch.h"
29 #include "bumpalloc.h"
30 #include "diacrit.h"
31 #include "error.h"
32 #include "regex.h"
34 /* The official name of this program (e.g., no `g' prefix). */
35 #define PROGRAM_NAME "ptx"
37 #define AUTHORS "François Pinard"
39 /* Number of possible characters in a byte. */
40 #define CHAR_SET_SIZE 256
42 #define ISODIGIT(C) ((C) >= '0' && (C) <= '7')
43 #define HEXTOBIN(C) ((C) >= 'a' && (C) <= 'f' ? (C)-'a'+10 \
44 : (C) >= 'A' && (C) <= 'F' ? (C)-'A'+10 : (C)-'0')
45 #define OCTTOBIN(C) ((C) - '0')
47 /* Debugging the memory allocator. */
49 #if WITH_DMALLOC
50 # define MALLOC_FUNC_CHECK 1
51 # include <dmalloc.h>
52 #endif
54 /* Global definitions. */
56 /* Reallocation step when swallowing non regular files. The value is not
57 the actual reallocation step, but its base two logarithm. */
58 #define SWALLOW_REALLOC_LOG 12
60 /* Imported from "regex.c". */
61 #define Sword 1
63 /* The name this program was run with. */
64 char *program_name;
66 /* Program options. */
68 enum Format
70 UNKNOWN_FORMAT, /* output format still unknown */
71 DUMB_FORMAT, /* output for a dumb terminal */
72 ROFF_FORMAT, /* output for `troff' or `nroff' */
73 TEX_FORMAT /* output for `TeX' or `LaTeX' */
76 int gnu_extensions = 1; /* trigger all GNU extensions */
77 int auto_reference = 0; /* references are `file_name:line_number:' */
78 int input_reference = 0; /* references at beginning of input lines */
79 int right_reference = 0; /* output references after right context */
80 int line_width = 72; /* output line width in characters */
81 int gap_size = 3; /* number of spaces between output fields */
82 const char *truncation_string = "/";
83 /* string used to mark line truncations */
84 const char *macro_name = "xx"; /* macro name for roff or TeX output */
85 enum Format output_format = UNKNOWN_FORMAT;
86 /* output format */
88 int ignore_case = 0; /* fold lower to upper case for sorting */
89 const char *context_regex_string = NULL;
90 /* raw regex for end of context */
91 const char *word_regex_string = NULL;
92 /* raw regex for a keyword */
93 const char *break_file = NULL; /* name of the `Break characters' file */
94 const char *only_file = NULL; /* name of the `Only words' file */
95 const char *ignore_file = NULL; /* name of the `Ignore words' file */
97 /* A BLOCK delimit a region in memory of arbitrary size, like the copy of a
98 whole file. A WORD is something smaller, its length should fit in a
99 short integer. A WORD_TABLE may contain several WORDs. */
101 typedef struct
103 char *start; /* pointer to beginning of region */
104 char *end; /* pointer to end + 1 of region */
106 BLOCK;
108 typedef struct
110 char *start; /* pointer to beginning of region */
111 short size; /* length of the region */
113 WORD;
115 typedef struct
117 WORD *start; /* array of WORDs */
118 size_t length; /* number of entries */
120 WORD_TABLE;
122 /* Pattern description tables. */
124 /* For each character, provide its folded equivalent. */
125 unsigned char folded_chars[CHAR_SET_SIZE];
127 /* For each character, indicate if it is part of a word. */
128 char syntax_table[CHAR_SET_SIZE];
129 char *re_syntax_table = syntax_table;
131 /* Compiled regex for end of context. */
132 struct re_pattern_buffer *context_regex;
134 /* End of context pattern register indices. */
135 struct re_registers context_regs;
137 /* Compiled regex for a keyword. */
138 struct re_pattern_buffer *word_regex;
140 /* Keyword pattern register indices. */
141 struct re_registers word_regs;
143 /* A word characters fastmap is used only when no word regexp has been
144 provided. A word is then made up of a sequence of one or more characters
145 allowed by the fastmap. Contains !0 if character allowed in word. Not
146 only this is faster in most cases, but it simplifies the implementation
147 of the Break files. */
148 char word_fastmap[CHAR_SET_SIZE];
150 /* Maximum length of any word read. */
151 int maximum_word_length;
153 /* Maximum width of any reference used. */
154 int reference_max_width;
156 /* Ignore and Only word tables. */
158 WORD_TABLE ignore_table; /* table of words to ignore */
159 WORD_TABLE only_table; /* table of words to select */
161 #define ALLOC_NEW_WORD(table) \
162 BUMP_ALLOC ((table)->start, (table)->length, 8, WORD)
164 /* Source text table, and scanning macros. */
166 int number_input_files; /* number of text input files */
167 int total_line_count; /* total number of lines seen so far */
168 const char **input_file_name; /* array of text input file names */
169 int *file_line_count; /* array of `total_line_count' values at end */
171 BLOCK text_buffer; /* file to study */
172 char *text_buffer_maxend; /* allocated end of text_buffer */
174 /* SKIP_NON_WHITE used only for getting or skipping the reference. */
176 #define SKIP_NON_WHITE(cursor, limit) \
177 while (cursor < limit && !ISSPACE(*cursor)) \
178 cursor++
180 #define SKIP_WHITE(cursor, limit) \
181 while (cursor < limit && ISSPACE(*cursor)) \
182 cursor++
184 #define SKIP_WHITE_BACKWARDS(cursor, start) \
185 while (cursor > start && ISSPACE(cursor[-1])) \
186 cursor--
188 #define SKIP_SOMETHING(cursor, limit) \
189 if (word_regex_string) \
191 int count; \
192 count = re_match (word_regex, cursor, limit - cursor, 0, NULL); \
193 cursor += count <= 0 ? 1 : count; \
195 else if (word_fastmap[(unsigned char) *cursor]) \
196 while (cursor < limit && word_fastmap[(unsigned char) *cursor]) \
197 cursor++; \
198 else \
199 cursor++
201 /* Occurrences table.
203 The `keyword' pointer provides the central word, which is surrounded
204 by a left context and a right context. The `keyword' and `length'
205 field allow full 8-bit characters keys, even including NULs. At other
206 places in this program, the name `keyafter' refers to the keyword
207 followed by its right context.
209 The left context does not extend, towards the beginning of the file,
210 further than a distance given by the `left' value. This value is
211 relative to the keyword beginning, it is usually negative. This
212 insures that, except for white space, we will never have to backward
213 scan the source text, when it is time to generate the final output
214 lines.
216 The right context, indirectly attainable through the keyword end, does
217 not extend, towards the end of the file, further than a distance given
218 by the `right' value. This value is relative to the keyword
219 beginning, it is usually positive.
221 When automatic references are used, the `reference' value is the
222 overall line number in all input files read so far, in this case, it
223 is of type (int). When input references are used, the `reference'
224 value indicates the distance between the keyword beginning and the
225 start of the reference field, it is of type (DELTA) and usually
226 negative. */
228 typedef short DELTA; /* to hold displacement within one context */
230 typedef struct
232 WORD key; /* description of the keyword */
233 DELTA left; /* distance to left context start */
234 DELTA right; /* distance to right context end */
235 int reference; /* reference descriptor */
237 OCCURS;
239 /* The various OCCURS tables are indexed by the language. But the time
240 being, there is no such multiple language support. */
242 OCCURS *occurs_table[1]; /* all words retained from the read text */
243 size_t number_of_occurs[1]; /* number of used slots in occurs_table */
245 #define ALLOC_NEW_OCCURS(language) \
246 BUMP_ALLOC (occurs_table[language], number_of_occurs[language], 9, OCCURS)
248 /* Communication among output routines. */
250 /* Indicate if special output processing is requested for each character. */
251 char edited_flag[CHAR_SET_SIZE];
253 int half_line_width; /* half of line width, reference excluded */
254 int before_max_width; /* maximum width of before field */
255 int keyafter_max_width; /* maximum width of keyword-and-after field */
256 int truncation_string_length; /* length of string used to flag truncation */
258 /* When context is limited by lines, wraparound may happen on final output:
259 the `head' pointer gives access to some supplementary left context which
260 will be seen at the end of the output line, the `tail' pointer gives
261 access to some supplementary right context which will be seen at the
262 beginning of the output line. */
264 BLOCK tail; /* tail field */
265 int tail_truncation; /* flag truncation after the tail field */
267 BLOCK before; /* before field */
268 int before_truncation; /* flag truncation before the before field */
270 BLOCK keyafter; /* keyword-and-after field */
271 int keyafter_truncation; /* flag truncation after the keyafter field */
273 BLOCK head; /* head field */
274 int head_truncation; /* flag truncation before the head field */
276 BLOCK reference; /* reference field for input reference mode */
278 /* Miscellaneous routines. */
280 /*------------------------------------------------------.
281 | Duplicate string STRING, while evaluating \-escapes. |
282 `------------------------------------------------------*/
284 /* Loosely adapted from GNU sh-utils printf.c code. */
286 static char *
287 copy_unescaped_string (const char *string)
289 char *result; /* allocated result */
290 char *cursor; /* cursor in result */
291 int value; /* value of \nnn escape */
292 int length; /* length of \nnn escape */
294 result = xmalloc (strlen (string) + 1);
295 cursor = result;
297 while (*string)
298 if (*string == '\\')
300 string++;
301 switch (*string)
303 case 'x': /* \xhhh escape, 3 chars maximum */
304 value = 0;
305 for (length = 0, string++;
306 length < 3 && ISXDIGIT (*string);
307 length++, string++)
308 value = value * 16 + HEXTOBIN (*string);
309 if (length == 0)
311 *cursor++ = '\\';
312 *cursor++ = 'x';
314 else
315 *cursor++ = value;
316 break;
318 case '0': /* \0ooo escape, 3 chars maximum */
319 value = 0;
320 for (length = 0, string++;
321 length < 3 && ISODIGIT (*string);
322 length++, string++)
323 value = value * 8 + OCTTOBIN (*string);
324 *cursor++ = value;
325 break;
327 case 'a': /* alert */
328 #if __STDC__
329 *cursor++ = '\a';
330 #else
331 *cursor++ = 7;
332 #endif
333 string++;
334 break;
336 case 'b': /* backspace */
337 *cursor++ = '\b';
338 string++;
339 break;
341 case 'c': /* cancel the rest of the output */
342 while (*string)
343 string++;
344 break;
346 case 'f': /* form feed */
347 *cursor++ = '\f';
348 string++;
349 break;
351 case 'n': /* new line */
352 *cursor++ = '\n';
353 string++;
354 break;
356 case 'r': /* carriage return */
357 *cursor++ = '\r';
358 string++;
359 break;
361 case 't': /* horizontal tab */
362 *cursor++ = '\t';
363 string++;
364 break;
366 case 'v': /* vertical tab */
367 #if __STDC__
368 *cursor++ = '\v';
369 #else
370 *cursor++ = 11;
371 #endif
372 string++;
373 break;
375 default:
376 *cursor++ = '\\';
377 *cursor++ = *string++;
378 break;
381 else
382 *cursor++ = *string++;
384 *cursor = '\0';
385 return result;
388 /*-------------------------------------------------------------------.
389 | Compile the regex represented by STRING, diagnose and abort if any |
390 | error. Returns the compiled regex structure. |
391 `-------------------------------------------------------------------*/
393 static struct re_pattern_buffer *
394 alloc_and_compile_regex (const char *string)
396 struct re_pattern_buffer *pattern; /* newly allocated structure */
397 const char *message; /* error message returned by regex.c */
399 pattern = (struct re_pattern_buffer *)
400 xmalloc (sizeof (struct re_pattern_buffer));
401 memset (pattern, 0, sizeof (struct re_pattern_buffer));
403 pattern->buffer = NULL;
404 pattern->allocated = 0;
405 pattern->translate = ignore_case ? (char *) folded_chars : NULL;
406 pattern->fastmap = (char *) xmalloc ((size_t) CHAR_SET_SIZE);
408 message = re_compile_pattern (string, (int) strlen (string), pattern);
409 if (message)
410 error (EXIT_FAILURE, 0, _("%s (for regexp `%s')"), message, string);
412 /* The fastmap should be compiled before `re_match'. The following
413 call is not mandatory, because `re_search' is always called sooner,
414 and it compiles the fastmap if this has not been done yet. */
416 re_compile_fastmap (pattern);
418 /* Do not waste extra allocated space. */
420 if (pattern->allocated > pattern->used)
422 pattern->buffer
423 = (unsigned char *) xrealloc (pattern->buffer, (size_t) pattern->used);
424 pattern->allocated = pattern->used;
427 return pattern;
430 /*------------------------------------------------------------------------.
431 | This will initialize various tables for pattern match and compiles some |
432 | regexps. |
433 `------------------------------------------------------------------------*/
435 static void
436 initialize_regex (void)
438 int character; /* character value */
440 /* Initialize the regex syntax table. */
442 for (character = 0; character < CHAR_SET_SIZE; character++)
443 syntax_table[character] = ISALPHA (character) ? Sword : 0;
445 /* Initialize the case folding table. */
447 if (ignore_case)
448 for (character = 0; character < CHAR_SET_SIZE; character++)
449 folded_chars[character] = TOUPPER (character);
451 /* Unless the user already provided a description of the end of line or
452 end of sentence sequence, select an end of line sequence to compile.
453 If the user provided an empty definition, thus disabling end of line
454 or sentence feature, make it NULL to speed up tests. If GNU
455 extensions are enabled, use end of sentence like in GNU emacs. If
456 disabled, use end of lines. */
458 if (context_regex_string)
460 if (!*context_regex_string)
461 context_regex_string = NULL;
463 else if (gnu_extensions && !input_reference)
464 context_regex_string = "[.?!][]\"')}]*\\($\\|\t\\| \\)[ \t\n]*";
465 else
466 context_regex_string = "\n";
468 if (context_regex_string)
469 context_regex = alloc_and_compile_regex (context_regex_string);
471 /* If the user has already provided a non-empty regexp to describe
472 words, compile it. Else, unless this has already been done through
473 a user provided Break character file, construct a fastmap of
474 characters that may appear in a word. If GNU extensions enabled,
475 include only letters of the underlying character set. If disabled,
476 include almost everything, even punctuations; stop only on white
477 space. */
479 if (word_regex_string && *word_regex_string)
480 word_regex = alloc_and_compile_regex (word_regex_string);
481 else if (!break_file)
483 if (gnu_extensions)
486 /* Simulate \w+. */
488 for (character = 0; character < CHAR_SET_SIZE; character++)
489 word_fastmap[character] = ISALPHA (character) ? 1 : 0;
491 else
494 /* Simulate [^ \t\n]+. */
496 memset (word_fastmap, 1, CHAR_SET_SIZE);
497 word_fastmap[' '] = 0;
498 word_fastmap['\t'] = 0;
499 word_fastmap['\n'] = 0;
504 /*------------------------------------------------------------------------.
505 | This routine will attempt to swallow a whole file name FILE_NAME into a |
506 | contiguous region of memory and return a description of it into BLOCK. |
507 | Standard input is assumed whenever FILE_NAME is NULL, empty or "-". |
509 | Previously, in some cases, white space compression was attempted while |
510 | inputting text. This was defeating some regexps like default end of |
511 | sentence, which checks for two consecutive spaces. If white space |
512 | compression is ever reinstated, it should be in output routines. |
513 `------------------------------------------------------------------------*/
515 static void
516 swallow_file_in_memory (const char *file_name, BLOCK *block)
518 int file_handle; /* file descriptor number */
519 struct stat stat_block; /* stat block for file */
520 size_t allocated_length; /* allocated length of memory buffer */
521 size_t used_length; /* used length in memory buffer */
522 int read_length; /* number of character gotten on last read */
524 /* As special cases, a file name which is NULL or "-" indicates standard
525 input, which is already opened. In all other cases, open the file from
526 its name. */
528 if (!file_name || !*file_name || strcmp (file_name, "-") == 0)
529 file_handle = fileno (stdin);
530 else
531 if ((file_handle = open (file_name, O_RDONLY)) < 0)
532 error (EXIT_FAILURE, errno, "%s", file_name);
534 /* If the file is a plain, regular file, allocate the memory buffer all at
535 once and swallow the file in one blow. In other cases, read the file
536 repeatedly in smaller chunks until we have it all, reallocating memory
537 once in a while, as we go. */
539 if (fstat (file_handle, &stat_block) < 0)
540 error (EXIT_FAILURE, errno, "%s", file_name);
542 if (S_ISREG (stat_block.st_mode))
544 size_t in_memory_size;
546 block->start = (char *) xmalloc ((size_t) stat_block.st_size);
548 if ((in_memory_size = read (file_handle,
549 block->start, (size_t) stat_block.st_size))
550 != stat_block.st_size)
552 #if MSDOS
553 /* On MSDOS, in memory size may be smaller than the file
554 size, because of end of line conversions. But it can
555 never be smaller than half the file size, because the
556 minimum is when all lines are empty and terminated by
557 CR+LF. */
558 if (in_memory_size != (size_t)-1
559 && in_memory_size >= stat_block.st_size / 2)
560 block->start = (char *) xrealloc (block->start, in_memory_size);
561 else
562 #endif /* not MSDOS */
564 error (EXIT_FAILURE, errno, "%s", file_name);
566 block->end = block->start + in_memory_size;
568 else
570 block->start = (char *) xmalloc ((size_t) 1 << SWALLOW_REALLOC_LOG);
571 used_length = 0;
572 allocated_length = (1 << SWALLOW_REALLOC_LOG);
574 while (read_length = read (file_handle,
575 block->start + used_length,
576 allocated_length - used_length),
577 read_length > 0)
579 used_length += read_length;
580 if (used_length == allocated_length)
582 allocated_length += (1 << SWALLOW_REALLOC_LOG);
583 block->start
584 = (char *) xrealloc (block->start, allocated_length);
588 if (read_length < 0)
589 error (EXIT_FAILURE, errno, "%s", file_name);
591 block->end = block->start + used_length;
594 /* Close the file, but only if it was not the standard input. */
596 if (file_handle != fileno (stdin))
597 close (file_handle);
600 /* Sort and search routines. */
602 /*--------------------------------------------------------------------------.
603 | Compare two words, FIRST and SECOND, and return 0 if they are identical. |
604 | Return less than 0 if the first word goes before the second; return |
605 | greater than 0 if the first word goes after the second. |
607 | If a word is indeed a prefix of the other, the shorter should go first. |
608 `--------------------------------------------------------------------------*/
610 static int
611 compare_words (const void *void_first, const void *void_second)
613 #define first ((const WORD *) void_first)
614 #define second ((const WORD *) void_second)
615 int length; /* minimum of two lengths */
616 int counter; /* cursor in words */
617 int value; /* value of comparison */
619 length = first->size < second->size ? first->size : second->size;
621 if (ignore_case)
623 for (counter = 0; counter < length; counter++)
625 value = (folded_chars [(unsigned char) (first->start[counter])]
626 - folded_chars [(unsigned char) (second->start[counter])]);
627 if (value != 0)
628 return value;
631 else
633 for (counter = 0; counter < length; counter++)
635 value = ((unsigned char) first->start[counter]
636 - (unsigned char) second->start[counter]);
637 if (value != 0)
638 return value;
642 return first->size - second->size;
643 #undef first
644 #undef second
647 /*-----------------------------------------------------------------------.
648 | Decides which of two OCCURS, FIRST or SECOND, should lexicographically |
649 | go first. In case of a tie, preserve the original order through a |
650 | pointer comparison. |
651 `-----------------------------------------------------------------------*/
653 static int
654 compare_occurs (const void *void_first, const void *void_second)
656 #define first ((const OCCURS *) void_first)
657 #define second ((const OCCURS *) void_second)
658 int value;
660 value = compare_words (&first->key, &second->key);
661 return value == 0 ? first->key.start - second->key.start : value;
662 #undef first
663 #undef second
666 /*------------------------------------------------------------.
667 | Return !0 if WORD appears in TABLE. Uses a binary search. |
668 `------------------------------------------------------------*/
670 static int
671 search_table (WORD *word, WORD_TABLE *table)
673 int lowest; /* current lowest possible index */
674 int highest; /* current highest possible index */
675 int middle; /* current middle index */
676 int value; /* value from last comparison */
678 lowest = 0;
679 highest = table->length - 1;
680 while (lowest <= highest)
682 middle = (lowest + highest) / 2;
683 value = compare_words (word, table->start + middle);
684 if (value < 0)
685 highest = middle - 1;
686 else if (value > 0)
687 lowest = middle + 1;
688 else
689 return 1;
691 return 0;
694 /*---------------------------------------------------------------------.
695 | Sort the whole occurs table in memory. Presumably, `qsort' does not |
696 | take intermediate copies or table elements, so the sort will be |
697 | stabilized throughout the comparison routine. |
698 `---------------------------------------------------------------------*/
700 static void
701 sort_found_occurs (void)
704 /* Only one language for the time being. */
706 qsort (occurs_table[0], number_of_occurs[0], sizeof (OCCURS),
707 compare_occurs);
710 /* Parameter files reading routines. */
712 /*----------------------------------------------------------------------.
713 | Read a file named FILE_NAME, containing a set of break characters. |
714 | Build a content to the array word_fastmap in which all characters are |
715 | allowed except those found in the file. Characters may be repeated. |
716 `----------------------------------------------------------------------*/
718 static void
719 digest_break_file (const char *file_name)
721 BLOCK file_contents; /* to receive a copy of the file */
722 char *cursor; /* cursor in file copy */
724 swallow_file_in_memory (file_name, &file_contents);
726 /* Make the fastmap and record the file contents in it. */
728 memset (word_fastmap, 1, CHAR_SET_SIZE);
729 for (cursor = file_contents.start; cursor < file_contents.end; cursor++)
730 word_fastmap[(unsigned char) *cursor] = 0;
732 if (!gnu_extensions)
735 /* If GNU extensions are enabled, the only way to avoid newline as
736 a break character is to write all the break characters in the
737 file with no newline at all, not even at the end of the file.
738 If disabled, spaces, tabs and newlines are always considered as
739 break characters even if not included in the break file. */
741 word_fastmap[' '] = 0;
742 word_fastmap['\t'] = 0;
743 word_fastmap['\n'] = 0;
746 /* Return the space of the file, which is no more required. */
748 free (file_contents.start);
751 /*-----------------------------------------------------------------------.
752 | Read a file named FILE_NAME, containing one word per line, then |
753 | construct in TABLE a table of WORD descriptors for them. The routine |
754 | swallows the whole file in memory; this is at the expense of space |
755 | needed for newlines, which are useless; however, the reading is fast. |
756 `-----------------------------------------------------------------------*/
758 static void
759 digest_word_file (const char *file_name, WORD_TABLE *table)
761 BLOCK file_contents; /* to receive a copy of the file */
762 char *cursor; /* cursor in file copy */
763 char *word_start; /* start of the current word */
765 swallow_file_in_memory (file_name, &file_contents);
767 table->start = NULL;
768 table->length = 0;
770 /* Read the whole file. */
772 cursor = file_contents.start;
773 while (cursor < file_contents.end)
776 /* Read one line, and save the word in contains. */
778 word_start = cursor;
779 while (cursor < file_contents.end && *cursor != '\n')
780 cursor++;
782 /* Record the word in table if it is not empty. */
784 if (cursor > word_start)
786 ALLOC_NEW_WORD (table);
787 table->start[table->length].start = word_start;
788 table->start[table->length].size = cursor - word_start;
789 table->length++;
792 /* This test allows for an incomplete line at end of file. */
794 if (cursor < file_contents.end)
795 cursor++;
798 /* Finally, sort all the words read. */
800 qsort (table->start, table->length, (size_t) sizeof (WORD), compare_words);
803 /* Keyword recognition and selection. */
805 /*----------------------------------------------------------------------.
806 | For each keyword in the source text, constructs an OCCURS structure. |
807 `----------------------------------------------------------------------*/
809 static void
810 find_occurs_in_text (void)
812 char *cursor; /* for scanning the source text */
813 char *scan; /* for scanning the source text also */
814 char *line_start; /* start of the current input line */
815 char *line_scan; /* newlines scanned until this point */
816 int reference_length; /* length of reference in input mode */
817 WORD possible_key; /* possible key, to ease searches */
818 OCCURS *occurs_cursor; /* current OCCURS under construction */
820 char *context_start; /* start of left context */
821 char *context_end; /* end of right context */
822 char *word_start; /* start of word */
823 char *word_end; /* end of word */
824 char *next_context_start; /* next start of left context */
826 /* reference_length is always used within `if (input_reference)'.
827 However, GNU C diagnoses that it may be used uninitialized. The
828 following assignment is merely to shut it up. */
830 reference_length = 0;
832 /* Tracking where lines start is helpful for reference processing. In
833 auto reference mode, this allows counting lines. In input reference
834 mode, this permits finding the beginning of the references.
836 The first line begins with the file, skip immediately this very first
837 reference in input reference mode, to help further rejection any word
838 found inside it. Also, unconditionally assigning these variable has
839 the happy effect of shutting up lint. */
841 line_start = text_buffer.start;
842 line_scan = line_start;
843 if (input_reference)
845 SKIP_NON_WHITE (line_scan, text_buffer.end);
846 reference_length = line_scan - line_start;
847 SKIP_WHITE (line_scan, text_buffer.end);
850 /* Process the whole buffer, one line or one sentence at a time. */
852 for (cursor = text_buffer.start;
853 cursor < text_buffer.end;
854 cursor = next_context_start)
857 /* `context_start' gets initialized before the processing of each
858 line, or once for the whole buffer if no end of line or sentence
859 sequence separator. */
861 context_start = cursor;
863 /* If a end of line or end of sentence sequence is defined and
864 non-empty, `next_context_start' will be recomputed to be the end of
865 each line or sentence, before each one is processed. If no such
866 sequence, then `next_context_start' is set at the end of the whole
867 buffer, which is then considered to be a single line or sentence.
868 This test also accounts for the case of an incomplete line or
869 sentence at the end of the buffer. */
871 if (context_regex_string
872 && (re_search (context_regex, cursor, text_buffer.end - cursor,
873 0, text_buffer.end - cursor, &context_regs)
874 >= 0))
875 next_context_start = cursor + context_regs.end[0];
877 else
878 next_context_start = text_buffer.end;
880 /* Include the separator into the right context, but not any suffix
881 white space in this separator; this insures it will be seen in
882 output and will not take more space than necessary. */
884 context_end = next_context_start;
885 SKIP_WHITE_BACKWARDS (context_end, context_start);
887 /* Read and process a single input line or sentence, one word at a
888 time. */
890 while (1)
892 if (word_regex)
894 /* If a word regexp has been compiled, use it to skip at the
895 beginning of the next word. If there is no such word, exit
896 the loop. */
899 if (re_search (word_regex, cursor, context_end - cursor,
900 0, context_end - cursor, &word_regs)
901 < 0)
902 break;
903 word_start = cursor + word_regs.start[0];
904 word_end = cursor + word_regs.end[0];
906 else
908 /* Avoid re_search and use the fastmap to skip to the
909 beginning of the next word. If there is no more word in
910 the buffer, exit the loop. */
913 scan = cursor;
914 while (scan < context_end
915 && !word_fastmap[(unsigned char) *scan])
916 scan++;
918 if (scan == context_end)
919 break;
921 word_start = scan;
923 while (scan < context_end
924 && word_fastmap[(unsigned char) *scan])
925 scan++;
927 word_end = scan;
930 /* Skip right to the beginning of the found word. */
932 cursor = word_start;
934 /* Skip any zero length word. Just advance a single position,
935 then go fetch the next word. */
937 if (word_end == word_start)
939 cursor++;
940 continue;
943 /* This is a genuine, non empty word, so save it as a possible
944 key. Then skip over it. Also, maintain the maximum length of
945 all words read so far. It is mandatory to take the maximum
946 length of all words in the file, without considering if they
947 are actually kept or rejected, because backward jumps at output
948 generation time may fall in *any* word. */
950 possible_key.start = cursor;
951 possible_key.size = word_end - word_start;
952 cursor += possible_key.size;
954 if (possible_key.size > maximum_word_length)
955 maximum_word_length = possible_key.size;
957 /* In input reference mode, update `line_start' from its previous
958 value. Count the lines just in case auto reference mode is
959 also selected. If it happens that the word just matched is
960 indeed part of a reference; just ignore it. */
962 if (input_reference)
964 while (line_scan < possible_key.start)
965 if (*line_scan == '\n')
967 total_line_count++;
968 line_scan++;
969 line_start = line_scan;
970 SKIP_NON_WHITE (line_scan, text_buffer.end);
971 reference_length = line_scan - line_start;
973 else
974 line_scan++;
975 if (line_scan > possible_key.start)
976 continue;
979 /* Ignore the word if an `Ignore words' table exists and if it is
980 part of it. Also ignore the word if an `Only words' table and
981 if it is *not* part of it.
983 It is allowed that both tables be used at once, even if this
984 may look strange for now. Just ignore a word that would appear
985 in both. If regexps are eventually implemented for these
986 tables, the Ignore table could then reject words that would
987 have been previously accepted by the Only table. */
989 if (ignore_file && search_table (&possible_key, &ignore_table))
990 continue;
991 if (only_file && !search_table (&possible_key, &only_table))
992 continue;
994 /* A non-empty word has been found. First of all, insure
995 proper allocation of the next OCCURS, and make a pointer to
996 where it will be constructed. */
998 ALLOC_NEW_OCCURS (0);
999 occurs_cursor = occurs_table[0] + number_of_occurs[0];
1001 /* Define the refence field, if any. */
1003 if (auto_reference)
1006 /* While auto referencing, update `line_start' from its
1007 previous value, counting lines as we go. If input
1008 referencing at the same time, `line_start' has been
1009 advanced earlier, and the following loop is never really
1010 executed. */
1012 while (line_scan < possible_key.start)
1013 if (*line_scan == '\n')
1015 total_line_count++;
1016 line_scan++;
1017 line_start = line_scan;
1018 SKIP_NON_WHITE (line_scan, text_buffer.end);
1020 else
1021 line_scan++;
1023 occurs_cursor->reference = total_line_count;
1025 else if (input_reference)
1028 /* If only input referencing, `line_start' has been computed
1029 earlier to detect the case the word matched would be part
1030 of the reference. The reference position is simply the
1031 value of `line_start'. */
1033 occurs_cursor->reference
1034 = (DELTA) (line_start - possible_key.start);
1035 if (reference_length > reference_max_width)
1036 reference_max_width = reference_length;
1039 /* Exclude the reference from the context in simple cases. */
1041 if (input_reference && line_start == context_start)
1043 SKIP_NON_WHITE (context_start, context_end);
1044 SKIP_WHITE (context_start, context_end);
1047 /* Completes the OCCURS structure. */
1049 occurs_cursor->key = possible_key;
1050 occurs_cursor->left = context_start - possible_key.start;
1051 occurs_cursor->right = context_end - possible_key.start;
1053 number_of_occurs[0]++;
1058 /* Formatting and actual output - service routines. */
1060 /*-----------------------------------------.
1061 | Prints some NUMBER of spaces on stdout. |
1062 `-----------------------------------------*/
1064 static void
1065 print_spaces (int number)
1067 int counter;
1069 for (counter = number; counter > 0; counter--)
1070 putchar (' ');
1073 /*-------------------------------------.
1074 | Prints the field provided by FIELD. |
1075 `-------------------------------------*/
1077 static void
1078 print_field (BLOCK field)
1080 char *cursor; /* Cursor in field to print */
1081 int character; /* Current character */
1082 int base; /* Base character, without diacritic */
1083 int diacritic; /* Diacritic code for the character */
1085 /* Whitespace is not really compressed. Instead, each white space
1086 character (tab, vt, ht etc.) is printed as one single space. */
1088 for (cursor = field.start; cursor < field.end; cursor++)
1090 character = (unsigned char) *cursor;
1091 if (edited_flag[character])
1094 /* First check if this is a diacriticized character.
1096 This works only for TeX. I do not know how diacriticized
1097 letters work with `roff'. Please someone explain it to me! */
1099 diacritic = todiac (character);
1100 if (diacritic != 0 && output_format == TEX_FORMAT)
1102 base = tobase (character);
1103 switch (diacritic)
1106 case 1: /* Latin diphthongs */
1107 switch (base)
1109 case 'o':
1110 fputs ("\\oe{}", stdout);
1111 break;
1113 case 'O':
1114 fputs ("\\OE{}", stdout);
1115 break;
1117 case 'a':
1118 fputs ("\\ae{}", stdout);
1119 break;
1121 case 'A':
1122 fputs ("\\AE{}", stdout);
1123 break;
1125 default:
1126 putchar (' ');
1128 break;
1130 case 2: /* Acute accent */
1131 printf ("\\'%s%c", (base == 'i' ? "\\" : ""), base);
1132 break;
1134 case 3: /* Grave accent */
1135 printf ("\\`%s%c", (base == 'i' ? "\\" : ""), base);
1136 break;
1138 case 4: /* Circumflex accent */
1139 printf ("\\^%s%c", (base == 'i' ? "\\" : ""), base);
1140 break;
1142 case 5: /* Diaeresis */
1143 printf ("\\\"%s%c", (base == 'i' ? "\\" : ""), base);
1144 break;
1146 case 6: /* Tilde accent */
1147 printf ("\\~%s%c", (base == 'i' ? "\\" : ""), base);
1148 break;
1150 case 7: /* Cedilla */
1151 printf ("\\c{%c}", base);
1152 break;
1154 case 8: /* Small circle beneath */
1155 switch (base)
1157 case 'a':
1158 fputs ("\\aa{}", stdout);
1159 break;
1161 case 'A':
1162 fputs ("\\AA{}", stdout);
1163 break;
1165 default:
1166 putchar (' ');
1168 break;
1170 case 9: /* Strike through */
1171 switch (base)
1173 case 'o':
1174 fputs ("\\o{}", stdout);
1175 break;
1177 case 'O':
1178 fputs ("\\O{}", stdout);
1179 break;
1181 default:
1182 putchar (' ');
1184 break;
1187 else
1189 /* This is not a diacritic character, so handle cases which are
1190 really specific to `roff' or TeX. All white space processing
1191 is done as the default case of this switch. */
1193 switch (character)
1195 case '"':
1196 /* In roff output format, double any quote. */
1197 putchar ('"');
1198 putchar ('"');
1199 break;
1201 case '$':
1202 case '%':
1203 case '&':
1204 case '#':
1205 case '_':
1206 /* In TeX output format, precede these with a backslash. */
1207 putchar ('\\');
1208 putchar (character);
1209 break;
1211 case '{':
1212 case '}':
1213 /* In TeX output format, precede these with a backslash and
1214 force mathematical mode. */
1215 printf ("$\\%c$", character);
1216 break;
1218 case '\\':
1219 /* In TeX output mode, request production of a backslash. */
1220 fputs ("\\backslash{}", stdout);
1221 break;
1223 default:
1224 /* Any other flagged character produces a single space. */
1225 putchar (' ');
1228 else
1229 putchar (*cursor);
1233 /* Formatting and actual output - planning routines. */
1235 /*--------------------------------------------------------------------.
1236 | From information collected from command line options and input file |
1237 | readings, compute and fix some output parameter values. |
1238 `--------------------------------------------------------------------*/
1240 static void
1241 fix_output_parameters (void)
1243 int file_index; /* index in text input file arrays */
1244 int line_ordinal; /* line ordinal value for reference */
1245 char ordinal_string[12]; /* edited line ordinal for reference */
1246 int reference_width; /* width for the whole reference */
1247 int character; /* character ordinal */
1248 const char *cursor; /* cursor in some constant strings */
1250 /* In auto reference mode, the maximum width of this field is
1251 precomputed and subtracted from the overall line width. Add one for
1252 the column which separate the file name from the line number. */
1254 if (auto_reference)
1256 reference_max_width = 0;
1257 for (file_index = 0; file_index < number_input_files; file_index++)
1259 line_ordinal = file_line_count[file_index] + 1;
1260 if (file_index > 0)
1261 line_ordinal -= file_line_count[file_index - 1];
1262 sprintf (ordinal_string, "%d", line_ordinal);
1263 reference_width = strlen (ordinal_string);
1264 if (input_file_name[file_index])
1265 reference_width += strlen (input_file_name[file_index]);
1266 if (reference_width > reference_max_width)
1267 reference_max_width = reference_width;
1269 reference_max_width++;
1270 reference.start = (char *) xmalloc ((size_t) reference_max_width + 1);
1273 /* If the reference appears to the left of the output line, reserve some
1274 space for it right away, including one gap size. */
1276 if ((auto_reference || input_reference) && !right_reference)
1277 line_width -= reference_max_width + gap_size;
1279 /* The output lines, minimally, will contain from left to right a left
1280 context, a gap, and a keyword followed by the right context with no
1281 special intervening gap. Half of the line width is dedicated to the
1282 left context and the gap, the other half is dedicated to the keyword
1283 and the right context; these values are computed once and for all here.
1284 There also are tail and head wrap around fields, used when the keyword
1285 is near the beginning or the end of the line, or when some long word
1286 cannot fit in, but leave place from wrapped around shorter words. The
1287 maximum width of these fields are recomputed separately for each line,
1288 on a case by case basis. It is worth noting that it cannot happen that
1289 both the tail and head fields are used at once. */
1291 half_line_width = line_width / 2;
1292 before_max_width = half_line_width - gap_size;
1293 keyafter_max_width = half_line_width;
1295 /* If truncation_string is the empty string, make it NULL to speed up
1296 tests. In this case, truncation_string_length will never get used, so
1297 there is no need to set it. */
1299 if (truncation_string && *truncation_string)
1300 truncation_string_length = strlen (truncation_string);
1301 else
1302 truncation_string = NULL;
1304 if (gnu_extensions)
1307 /* When flagging truncation at the left of the keyword, the
1308 truncation mark goes at the beginning of the before field,
1309 unless there is a head field, in which case the mark goes at the
1310 left of the head field. When flagging truncation at the right
1311 of the keyword, the mark goes at the end of the keyafter field,
1312 unless there is a tail field, in which case the mark goes at the
1313 end of the tail field. Only eight combination cases could arise
1314 for truncation marks:
1316 . None.
1317 . One beginning the before field.
1318 . One beginning the head field.
1319 . One ending the keyafter field.
1320 . One ending the tail field.
1321 . One beginning the before field, another ending the keyafter field.
1322 . One ending the tail field, another beginning the before field.
1323 . One ending the keyafter field, another beginning the head field.
1325 So, there is at most two truncation marks, which could appear both
1326 on the left side of the center of the output line, both on the
1327 right side, or one on either side. */
1329 before_max_width -= 2 * truncation_string_length;
1330 keyafter_max_width -= 2 * truncation_string_length;
1332 else
1335 /* I never figured out exactly how UNIX' ptx plans the output width
1336 of its various fields. If GNU extensions are disabled, do not
1337 try computing the field widths correctly; instead, use the
1338 following formula, which does not completely imitate UNIX' ptx,
1339 but almost. */
1341 keyafter_max_width -= 2 * truncation_string_length + 1;
1344 /* Compute which characters need special output processing. Initialize
1345 by flagging any white space character. Some systems do not consider
1346 form feed as a space character, but we do. */
1348 for (character = 0; character < CHAR_SET_SIZE; character++)
1349 edited_flag[character] = ISSPACE (character) != 0;
1350 edited_flag['\f'] = 1;
1352 /* Complete the special character flagging according to selected output
1353 format. */
1355 switch (output_format)
1357 case UNKNOWN_FORMAT:
1358 /* Should never happen. */
1360 case DUMB_FORMAT:
1361 break;
1363 case ROFF_FORMAT:
1365 /* `Quote' characters should be doubled. */
1367 edited_flag['"'] = 1;
1368 break;
1370 case TEX_FORMAT:
1372 /* Various characters need special processing. */
1374 for (cursor = "$%&#_{}\\"; *cursor; cursor++)
1375 edited_flag[(unsigned char) *cursor] = 1;
1377 /* Any character with 8th bit set will print to a single space, unless
1378 it is diacriticized. */
1380 for (character = 0200; character < CHAR_SET_SIZE; character++)
1381 edited_flag[character] = todiac (character) != 0;
1382 break;
1386 /*------------------------------------------------------------------.
1387 | Compute the position and length of all the output fields, given a |
1388 | pointer to some OCCURS. |
1389 `------------------------------------------------------------------*/
1391 static void
1392 define_all_fields (OCCURS *occurs)
1394 int tail_max_width; /* allowable width of tail field */
1395 int head_max_width; /* allowable width of head field */
1396 char *cursor; /* running cursor in source text */
1397 char *left_context_start; /* start of left context */
1398 char *right_context_end; /* end of right context */
1399 char *left_field_start; /* conservative start for `head'/`before' */
1400 int file_index; /* index in text input file arrays */
1401 const char *file_name; /* file name for reference */
1402 int line_ordinal; /* line ordinal for reference */
1404 /* Define `keyafter', start of left context and end of right context.
1405 `keyafter' starts at the saved position for keyword and extend to the
1406 right from the end of the keyword, eating separators or full words, but
1407 not beyond maximum allowed width for `keyafter' field or limit for the
1408 right context. Suffix spaces will be removed afterwards. */
1410 keyafter.start = occurs->key.start;
1411 keyafter.end = keyafter.start + occurs->key.size;
1412 left_context_start = keyafter.start + occurs->left;
1413 right_context_end = keyafter.start + occurs->right;
1415 cursor = keyafter.end;
1416 while (cursor < right_context_end
1417 && cursor <= keyafter.start + keyafter_max_width)
1419 keyafter.end = cursor;
1420 SKIP_SOMETHING (cursor, right_context_end);
1422 if (cursor <= keyafter.start + keyafter_max_width)
1423 keyafter.end = cursor;
1425 keyafter_truncation = truncation_string && keyafter.end < right_context_end;
1427 SKIP_WHITE_BACKWARDS (keyafter.end, keyafter.start);
1429 /* When the left context is wide, it might take some time to catch up from
1430 the left context boundary to the beginning of the `head' or `before'
1431 fields. So, in this case, to speed the catchup, we jump back from the
1432 keyword, using some secure distance, possibly falling in the middle of
1433 a word. A secure backward jump would be at least half the maximum
1434 width of a line, plus the size of the longest word met in the whole
1435 input. We conclude this backward jump by a skip forward of at least
1436 one word. In this manner, we should not inadvertently accept only part
1437 of a word. From the reached point, when it will be time to fix the
1438 beginning of `head' or `before' fields, we will skip forward words or
1439 delimiters until we get sufficiently near. */
1441 if (-occurs->left > half_line_width + maximum_word_length)
1443 left_field_start
1444 = keyafter.start - (half_line_width + maximum_word_length);
1445 SKIP_SOMETHING (left_field_start, keyafter.start);
1447 else
1448 left_field_start = keyafter.start + occurs->left;
1450 /* `before' certainly ends at the keyword, but not including separating
1451 spaces. It starts after than the saved value for the left context, by
1452 advancing it until it falls inside the maximum allowed width for the
1453 before field. There will be no prefix spaces either. `before' only
1454 advances by skipping single separators or whole words. */
1456 before.start = left_field_start;
1457 before.end = keyafter.start;
1458 SKIP_WHITE_BACKWARDS (before.end, before.start);
1460 while (before.start + before_max_width < before.end)
1461 SKIP_SOMETHING (before.start, before.end);
1463 if (truncation_string)
1465 cursor = before.start;
1466 SKIP_WHITE_BACKWARDS (cursor, text_buffer.start);
1467 before_truncation = cursor > left_context_start;
1469 else
1470 before_truncation = 0;
1472 SKIP_WHITE (before.start, text_buffer.end);
1474 /* The tail could not take more columns than what has been left in the
1475 left context field, and a gap is mandatory. It starts after the
1476 right context, and does not contain prefixed spaces. It ends at
1477 the end of line, the end of buffer or when the tail field is full,
1478 whichever comes first. It cannot contain only part of a word, and
1479 has no suffixed spaces. */
1481 tail_max_width
1482 = before_max_width - (before.end - before.start) - gap_size;
1484 if (tail_max_width > 0)
1486 tail.start = keyafter.end;
1487 SKIP_WHITE (tail.start, text_buffer.end);
1489 tail.end = tail.start;
1490 cursor = tail.end;
1491 while (cursor < right_context_end
1492 && cursor < tail.start + tail_max_width)
1494 tail.end = cursor;
1495 SKIP_SOMETHING (cursor, right_context_end);
1498 if (cursor < tail.start + tail_max_width)
1499 tail.end = cursor;
1501 if (tail.end > tail.start)
1503 keyafter_truncation = 0;
1504 tail_truncation = truncation_string && tail.end < right_context_end;
1506 else
1507 tail_truncation = 0;
1509 SKIP_WHITE_BACKWARDS (tail.end, tail.start);
1511 else
1514 /* No place left for a tail field. */
1516 tail.start = NULL;
1517 tail.end = NULL;
1518 tail_truncation = 0;
1521 /* `head' could not take more columns than what has been left in the right
1522 context field, and a gap is mandatory. It ends before the left
1523 context, and does not contain suffixed spaces. Its pointer is advanced
1524 until the head field has shrunk to its allowed width. It cannot
1525 contain only part of a word, and has no suffixed spaces. */
1527 head_max_width
1528 = keyafter_max_width - (keyafter.end - keyafter.start) - gap_size;
1530 if (head_max_width > 0)
1532 head.end = before.start;
1533 SKIP_WHITE_BACKWARDS (head.end, text_buffer.start);
1535 head.start = left_field_start;
1536 while (head.start + head_max_width < head.end)
1537 SKIP_SOMETHING (head.start, head.end);
1539 if (head.end > head.start)
1541 before_truncation = 0;
1542 head_truncation = (truncation_string
1543 && head.start > left_context_start);
1545 else
1546 head_truncation = 0;
1548 SKIP_WHITE (head.start, head.end);
1550 else
1553 /* No place left for a head field. */
1555 head.start = NULL;
1556 head.end = NULL;
1557 head_truncation = 0;
1560 if (auto_reference)
1563 /* Construct the reference text in preallocated space from the file
1564 name and the line number. Find out in which file the reference
1565 occurred. Standard input yields an empty file name. Insure line
1566 numbers are one based, even if they are computed zero based. */
1568 file_index = 0;
1569 while (file_line_count[file_index] < occurs->reference)
1570 file_index++;
1572 file_name = input_file_name[file_index];
1573 if (!file_name)
1574 file_name = "";
1576 line_ordinal = occurs->reference + 1;
1577 if (file_index > 0)
1578 line_ordinal -= file_line_count[file_index - 1];
1580 sprintf (reference.start, "%s:%d", file_name, line_ordinal);
1581 reference.end = reference.start + strlen (reference.start);
1583 else if (input_reference)
1586 /* Reference starts at saved position for reference and extends right
1587 until some white space is met. */
1589 reference.start = keyafter.start + (DELTA) occurs->reference;
1590 reference.end = reference.start;
1591 SKIP_NON_WHITE (reference.end, right_context_end);
1595 /* Formatting and actual output - control routines. */
1597 /*----------------------------------------------------------------------.
1598 | Output the current output fields as one line for `troff' or `nroff'. |
1599 `----------------------------------------------------------------------*/
1601 static void
1602 output_one_roff_line (void)
1604 /* Output the `tail' field. */
1606 printf (".%s \"", macro_name);
1607 print_field (tail);
1608 if (tail_truncation)
1609 fputs (truncation_string, stdout);
1610 putchar ('"');
1612 /* Output the `before' field. */
1614 fputs (" \"", stdout);
1615 if (before_truncation)
1616 fputs (truncation_string, stdout);
1617 print_field (before);
1618 putchar ('"');
1620 /* Output the `keyafter' field. */
1622 fputs (" \"", stdout);
1623 print_field (keyafter);
1624 if (keyafter_truncation)
1625 fputs (truncation_string, stdout);
1626 putchar ('"');
1628 /* Output the `head' field. */
1630 fputs (" \"", stdout);
1631 if (head_truncation)
1632 fputs (truncation_string, stdout);
1633 print_field (head);
1634 putchar ('"');
1636 /* Conditionally output the `reference' field. */
1638 if (auto_reference || input_reference)
1640 fputs (" \"", stdout);
1641 print_field (reference);
1642 putchar ('"');
1645 putchar ('\n');
1648 /*---------------------------------------------------------.
1649 | Output the current output fields as one line for `TeX'. |
1650 `---------------------------------------------------------*/
1652 static void
1653 output_one_tex_line (void)
1655 BLOCK key; /* key field, isolated */
1656 BLOCK after; /* after field, isolated */
1657 char *cursor; /* running cursor in source text */
1659 printf ("\\%s ", macro_name);
1660 fputs ("{", stdout);
1661 print_field (tail);
1662 fputs ("}{", stdout);
1663 print_field (before);
1664 fputs ("}{", stdout);
1665 key.start = keyafter.start;
1666 after.end = keyafter.end;
1667 cursor = keyafter.start;
1668 SKIP_SOMETHING (cursor, keyafter.end);
1669 key.end = cursor;
1670 after.start = cursor;
1671 print_field (key);
1672 fputs ("}{", stdout);
1673 print_field (after);
1674 fputs ("}{", stdout);
1675 print_field (head);
1676 fputs ("}", stdout);
1677 if (auto_reference || input_reference)
1679 fputs ("{", stdout);
1680 print_field (reference);
1681 fputs ("}", stdout);
1683 fputs ("\n", stdout);
1686 /*-------------------------------------------------------------------.
1687 | Output the current output fields as one line for a dumb terminal. |
1688 `-------------------------------------------------------------------*/
1690 static void
1691 output_one_dumb_line (void)
1693 if (!right_reference)
1695 if (auto_reference)
1698 /* Output the `reference' field, in such a way that GNU emacs
1699 next-error will handle it. The ending colon is taken from the
1700 gap which follows. */
1702 print_field (reference);
1703 putchar (':');
1704 print_spaces (reference_max_width
1705 + gap_size
1706 - (reference.end - reference.start)
1707 - 1);
1709 else
1712 /* Output the `reference' field and its following gap. */
1714 print_field (reference);
1715 print_spaces (reference_max_width
1716 + gap_size
1717 - (reference.end - reference.start));
1721 if (tail.start < tail.end)
1723 /* Output the `tail' field. */
1725 print_field (tail);
1726 if (tail_truncation)
1727 fputs (truncation_string, stdout);
1729 print_spaces (half_line_width - gap_size
1730 - (before.end - before.start)
1731 - (before_truncation ? truncation_string_length : 0)
1732 - (tail.end - tail.start)
1733 - (tail_truncation ? truncation_string_length : 0));
1735 else
1736 print_spaces (half_line_width - gap_size
1737 - (before.end - before.start)
1738 - (before_truncation ? truncation_string_length : 0));
1740 /* Output the `before' field. */
1742 if (before_truncation)
1743 fputs (truncation_string, stdout);
1744 print_field (before);
1746 print_spaces (gap_size);
1748 /* Output the `keyafter' field. */
1750 print_field (keyafter);
1751 if (keyafter_truncation)
1752 fputs (truncation_string, stdout);
1754 if (head.start < head.end)
1756 /* Output the `head' field. */
1758 print_spaces (half_line_width
1759 - (keyafter.end - keyafter.start)
1760 - (keyafter_truncation ? truncation_string_length : 0)
1761 - (head.end - head.start)
1762 - (head_truncation ? truncation_string_length : 0));
1763 if (head_truncation)
1764 fputs (truncation_string, stdout);
1765 print_field (head);
1767 else
1769 if ((auto_reference || input_reference) && right_reference)
1770 print_spaces (half_line_width
1771 - (keyafter.end - keyafter.start)
1772 - (keyafter_truncation ? truncation_string_length : 0));
1774 if ((auto_reference || input_reference) && right_reference)
1776 /* Output the `reference' field. */
1778 print_spaces (gap_size);
1779 print_field (reference);
1782 fputs ("\n", stdout);
1785 /*------------------------------------------------------------------------.
1786 | Scan the whole occurs table and, for each entry, output one line in the |
1787 | appropriate format. |
1788 `------------------------------------------------------------------------*/
1790 static void
1791 generate_all_output (void)
1793 int occurs_index; /* index of keyword entry being processed */
1794 OCCURS *occurs_cursor; /* current keyword entry being processed */
1796 /* The following assignments are useful to provide default values in case
1797 line contexts or references are not used, in which case these variables
1798 would never be computed. */
1800 tail.start = NULL;
1801 tail.end = NULL;
1802 tail_truncation = 0;
1804 head.start = NULL;
1805 head.end = NULL;
1806 head_truncation = 0;
1808 /* Loop over all keyword occurrences. */
1810 occurs_cursor = occurs_table[0];
1812 for (occurs_index = 0; occurs_index < number_of_occurs[0]; occurs_index++)
1814 /* Compute the exact size of every field and whenever truncation flags
1815 are present or not. */
1817 define_all_fields (occurs_cursor);
1819 /* Produce one output line according to selected format. */
1821 switch (output_format)
1823 case UNKNOWN_FORMAT:
1824 /* Should never happen. */
1826 case DUMB_FORMAT:
1827 output_one_dumb_line ();
1828 break;
1830 case ROFF_FORMAT:
1831 output_one_roff_line ();
1832 break;
1834 case TEX_FORMAT:
1835 output_one_tex_line ();
1836 break;
1839 /* Advance the cursor into the occurs table. */
1841 occurs_cursor++;
1845 /* Option decoding and main program. */
1847 /*------------------------------------------------------.
1848 | Print program identification and options, then exit. |
1849 `------------------------------------------------------*/
1851 void
1852 usage (int status)
1854 if (status != EXIT_SUCCESS)
1855 fprintf (stderr, _("Try `%s --help' for more information.\n"),
1856 program_name);
1857 else
1859 printf (_("\
1860 Usage: %s [OPTION]... [INPUT]... (without -G)\n\
1861 or: %s -G [OPTION]... [INPUT [OUTPUT]]\n"),
1862 program_name, program_name);
1863 fputs (_("\
1864 Mandatory arguments to long options are mandatory for short options too.\n\
1866 -A, --auto-reference output automatically generated references\n\
1867 -C, --copyright display Copyright and copying conditions\n\
1868 -G, --traditional behave more like System V `ptx'\n\
1869 -F, --flag-truncation=STRING use STRING for flagging line truncations\n\
1870 -M, --macro-name=STRING macro name to use instead of `xx'\n\
1871 -O, --format=roff generate output as roff directives\n\
1872 -R, --right-side-refs put references at right, not counted in -w\n\
1873 -S, --sentence-regexp=REGEXP for end of lines or end of sentences\n\
1874 -T, --format=tex generate output as TeX directives\n\
1875 -W, --word-regexp=REGEXP use REGEXP to match each keyword\n\
1876 -b, --break-file=FILE word break characters in this FILE\n\
1877 -f, --ignore-case fold lower case to upper case for sorting\n\
1878 -g, --gap-size=NUMBER gap size in columns between output fields\n\
1879 -i, --ignore-file=FILE read ignore word list from FILE\n\
1880 -o, --only-file=FILE read only word list from this FILE\n\
1881 -r, --references first field of each line is a reference\n\
1882 -t, --typeset-mode - not implemented -\n\
1883 -w, --width=NUMBER output width in columns, reference excluded\n\
1884 --help display this help and exit\n\
1885 --version output version information and exit\n\
1887 With no FILE or if FILE is -, read Standard Input. `-F /' by default.\n"),
1888 stdout);
1890 exit (status);
1893 /*----------------------------------------------------------------------.
1894 | Main program. Decode ARGC arguments passed through the ARGV array of |
1895 | strings, then launch execution. |
1896 `----------------------------------------------------------------------*/
1898 /* Long options equivalences. */
1899 static const struct option long_options[] =
1901 {"auto-reference", no_argument, NULL, 'A'},
1902 {"break-file", required_argument, NULL, 'b'},
1903 {"copyright", no_argument, NULL, 'C'},
1904 {"flag-truncation", required_argument, NULL, 'F'},
1905 {"ignore-case", no_argument, NULL, 'f'},
1906 {"gap-size", required_argument, NULL, 'g'},
1907 {"ignore-file", required_argument, NULL, 'i'},
1908 {"macro-name", required_argument, NULL, 'M'},
1909 {"only-file", required_argument, NULL, 'o'},
1910 {"references", no_argument, NULL, 'r'},
1911 {"right-side-refs", no_argument, NULL, 'R'},
1912 {"format", required_argument, NULL, 10},
1913 {"sentence-regexp", required_argument, NULL, 'S'},
1914 {"traditional", no_argument, NULL, 'G'},
1915 {"typeset-mode", no_argument, NULL, 't'},
1916 {"width", required_argument, NULL, 'w'},
1917 {"word-regexp", required_argument, NULL, 'W'},
1918 {GETOPT_HELP_OPTION_DECL},
1919 {GETOPT_VERSION_OPTION_DECL},
1920 {0, 0, 0, 0},
1923 static char const* const format_args[] =
1925 "roff", "tex", 0
1928 static enum Format const format_vals[] =
1930 ROFF_FORMAT, TEX_FORMAT
1934 main (int argc, char **argv)
1936 int optchar; /* argument character */
1937 int file_index; /* index in text input file arrays */
1939 /* Decode program options. */
1941 program_name = argv[0];
1942 setlocale (LC_ALL, "");
1943 bindtextdomain (PACKAGE, LOCALEDIR);
1944 textdomain (PACKAGE);
1946 atexit (close_stdout);
1948 #if HAVE_SETCHRCLASS
1949 setchrclass (NULL);
1950 #endif
1952 while (optchar = getopt_long (argc, argv, "ACF:GM:ORS:TW:b:i:fg:o:trw:",
1953 long_options, NULL),
1954 optchar != EOF)
1956 switch (optchar)
1958 default:
1959 usage (EXIT_FAILURE);
1961 case 0:
1962 break;
1964 case 'C':
1965 fputs (_("\
1966 This program is free software; you can redistribute it and/or modify\n\
1967 it under the terms of the GNU General Public License as published by\n\
1968 the Free Software Foundation; either version 2, or (at your option)\n\
1969 any later version.\n\
1971 This program is distributed in the hope that it will be useful,\n\
1972 but WITHOUT ANY WARRANTY; without even the implied warranty of\n\
1973 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n\
1974 GNU General Public License for more details.\n\
1976 You should have received a copy of the GNU General Public License\n\
1977 along with this program; if not, write to the Free Software Foundation,\n\
1978 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.\n"),
1979 stdout);
1981 exit (EXIT_SUCCESS);
1983 case 'G':
1984 gnu_extensions = 0;
1985 break;
1987 case 'b':
1988 break_file = optarg;
1989 break;
1991 case 'f':
1992 ignore_case = 1;
1993 break;
1995 case 'g':
1996 gap_size = atoi (optarg);
1997 break;
1999 case 'i':
2000 ignore_file = optarg;
2001 break;
2003 case 'o':
2004 only_file = optarg;
2005 break;
2007 case 'r':
2008 input_reference = 1;
2009 break;
2011 case 't':
2012 /* Yet to understand... */
2013 break;
2015 case 'w':
2016 line_width = atoi (optarg);
2017 break;
2019 case 'A':
2020 auto_reference = 1;
2021 break;
2023 case 'F':
2024 truncation_string = copy_unescaped_string (optarg);
2025 break;
2027 case 'M':
2028 macro_name = optarg;
2029 break;
2031 case 'O':
2032 output_format = ROFF_FORMAT;
2033 break;
2035 case 'R':
2036 right_reference = 1;
2037 break;
2039 case 'S':
2040 context_regex_string = copy_unescaped_string (optarg);
2041 break;
2043 case 'T':
2044 output_format = TEX_FORMAT;
2045 break;
2047 case 'W':
2048 word_regex_string = copy_unescaped_string (optarg);
2049 break;
2051 case 10:
2052 output_format = XARGMATCH ("--format", optarg,
2053 format_args, format_vals);
2054 case_GETOPT_HELP_CHAR;
2056 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
2060 /* Change the default Ignore file if one is defined. */
2062 #ifdef DEFAULT_IGNORE_FILE
2063 if (!ignore_file)
2064 ignore_file = DEFAULT_IGNORE_FILE;
2065 #endif
2067 /* Process remaining arguments. If GNU extensions are enabled, process
2068 all arguments as input parameters. If disabled, accept at most two
2069 arguments, the second of which is an output parameter. */
2071 if (optind == argc)
2074 /* No more argument simply means: read standard input. */
2076 input_file_name = (const char **) xmalloc (sizeof (const char *));
2077 file_line_count = (int *) xmalloc (sizeof (int));
2078 number_input_files = 1;
2079 input_file_name[0] = NULL;
2081 else if (gnu_extensions)
2083 number_input_files = argc - optind;
2084 input_file_name
2085 = (const char **) xmalloc (number_input_files * sizeof (const char *));
2086 file_line_count
2087 = (int *) xmalloc (number_input_files * sizeof (int));
2089 for (file_index = 0; file_index < number_input_files; file_index++)
2091 input_file_name[file_index] = argv[optind];
2092 if (!*argv[optind] || strcmp (argv[optind], "-") == 0)
2093 input_file_name[0] = NULL;
2094 else
2095 input_file_name[0] = argv[optind];
2096 optind++;
2099 else
2102 /* There is one necessary input file. */
2104 number_input_files = 1;
2105 input_file_name = (const char **) xmalloc (sizeof (const char *));
2106 file_line_count = (int *) xmalloc (sizeof (int));
2107 if (!*argv[optind] || strcmp (argv[optind], "-") == 0)
2108 input_file_name[0] = NULL;
2109 else
2110 input_file_name[0] = argv[optind];
2111 optind++;
2113 /* Redirect standard output, only if requested. */
2115 if (optind < argc)
2117 /* FIXME: don't fclose here? */
2118 fclose (stdout);
2119 if (fopen (argv[optind], "w") == NULL)
2120 error (EXIT_FAILURE, errno, "%s", argv[optind]);
2121 optind++;
2124 /* Diagnose any other argument as an error. */
2126 if (optind < argc)
2127 usage (EXIT_FAILURE);
2130 /* If the output format has not been explicitly selected, choose dumb
2131 terminal format if GNU extensions are enabled, else `roff' format. */
2133 if (output_format == UNKNOWN_FORMAT)
2134 output_format = gnu_extensions ? DUMB_FORMAT : ROFF_FORMAT;
2136 /* Initialize the main tables. */
2138 initialize_regex ();
2140 /* Read `Break character' file, if any. */
2142 if (break_file)
2143 digest_break_file (break_file);
2145 /* Read `Ignore words' file and `Only words' files, if any. If any of
2146 these files is empty, reset the name of the file to NULL, to avoid
2147 unnecessary calls to search_table. */
2149 if (ignore_file)
2151 digest_word_file (ignore_file, &ignore_table);
2152 if (ignore_table.length == 0)
2153 ignore_file = NULL;
2156 if (only_file)
2158 digest_word_file (only_file, &only_table);
2159 if (only_table.length == 0)
2160 only_file = NULL;
2163 /* Prepare to study all the input files. */
2165 number_of_occurs[0] = 0;
2166 total_line_count = 0;
2167 maximum_word_length = 0;
2168 reference_max_width = 0;
2170 for (file_index = 0; file_index < number_input_files; file_index++)
2173 /* Read the file in core, than study it. */
2175 swallow_file_in_memory (input_file_name[file_index], &text_buffer);
2176 find_occurs_in_text ();
2178 /* Maintain for each file how many lines has been read so far when its
2179 end is reached. Incrementing the count first is a simple kludge to
2180 handle a possible incomplete line at end of file. */
2182 total_line_count++;
2183 file_line_count[file_index] = total_line_count;
2186 /* Do the output process phase. */
2188 sort_found_occurs ();
2189 fix_output_parameters ();
2190 generate_all_output ();
2192 /* All done. */
2194 exit (EXIT_SUCCESS);