home *** CD-ROM | disk | FTP | other *** search
/ ProfitPress Mega CDROM2 …eeware (MSDOS)(1992)(Eng) / ProfitPress-MegaCDROM2.B6I / MISC / GNU / GPTX01AS.ZIP / GPTX.C < prev    next >
Encoding:
C/C++ Source or Header  |  1992-02-22  |  64.1 KB  |  2,232 lines

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