BSD 4_4 development
authorCSRG <csrg@ucbvax.Berkeley.EDU>
Tue, 8 Jan 1991 04:23:41 +0000 (20:23 -0800)
committerCSRG <csrg@ucbvax.Berkeley.EDU>
Tue, 8 Jan 1991 04:23:41 +0000 (20:23 -0800)
Work on file usr/src/contrib/emacs-18.57/man/makeinfo.c

Synthesized-from: CSRG/cd3/4.4

usr/src/contrib/emacs-18.57/man/makeinfo.c [new file with mode: 0644]

diff --git a/usr/src/contrib/emacs-18.57/man/makeinfo.c b/usr/src/contrib/emacs-18.57/man/makeinfo.c
new file mode 100644 (file)
index 0000000..08b3d03
--- /dev/null
@@ -0,0 +1,5285 @@
+/* Makeinfo -- convert texinfo format files into info files
+   Copyright (C) 1987 Free Software Foundation, Inc.
+
+This file is part of GNU Info.
+
+GNU Info is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 1, or (at your option)
+any later version.
+
+GNU Info is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Info; see the file COPYING.  If not, write to
+the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */
+\f
+/* **************************************************************** */
+/*                                                                 */
+/*                     Include File Declarations                   */
+/*                                                                 */
+/* **************************************************************** */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include <pwd.h>
+#include <errno.h>
+#include "getopt.h"
+
+#if defined (VMS)
+#include <perror.h>
+#endif
+
+#if defined (SYSV) || defined (VMS)
+#include <string.h>
+#else
+#include <strings.h>
+#endif
+
+#include <fcntl.h>
+#include <sys/file.h>
+
+#if defined (SYSV)
+#define bcopy(source, dest, count) memcpy (dest, source, count)
+#endif
+
+#if defined (__GNUC__)
+#define alloca __builtin_alloca
+#else
+#if defined (sparc)
+#include <alloca.h>
+#else
+extern char *alloca ();
+#endif
+#endif
+
+/* Forward declarations. */
+char *xmalloc (), *xrealloc ();
+extern int in_fixed_width_font;
+
+/* Some systems don't declare this function in pwd.h. */
+struct passwd *getpwnam ();
+
+\f
+/* **************************************************************** */
+/*                                                                 */
+/*                           Global Defines                        */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Error levels */
+#define NO_ERROR 0
+#define SYNTAX  2
+#define FATAL   4
+
+/* Boolean values. */
+#define true  1
+#define false 0
+typedef int boolean;
+
+/* How to allocate permanent storage for STRING. */
+#define savestring(x) \
+  ((char *)strcpy (xmalloc (1 + ((x) ? strlen (x) : 0)), (x) ? (x) : ""))
+
+/* C's standard macros don't check to make sure that the characters being
+   changed are within range.  So I have to check explicitly. */
+
+/* GNU Library doesn't have toupper().  Until GNU gets this fixed, I will
+   have to do it. */
+#ifndef toupper
+#define toupper(c) ((c) - 32)
+#endif
+
+#define coerce_to_upper(c) ((islower(c) ? toupper(c) : (c)))
+#define coerce_to_lower(c) ((isupper(c) ? tolower(c) : (c)))
+
+#define control_character_bit 0x40 /* %01000000, must be off. */
+#define meta_character_bit 0x080/* %10000000, must be on.  */
+#define CTL(c) ((c) & (~control_character_bit))
+#define UNCTL(c) coerce_to_upper(((c)|control_character_bit))
+#define META(c) ((c) | (meta_character_bit))
+#define UNMETA(c) ((c) & (~meta_character_bit))
+
+#define whitespace(c) (((c) == '\t') || ((c) == ' '))
+#define sentence_ender(c) ((c) == '.' || (c) == '?' || (c) == '!')
+#define cr_or_whitespace(c) (((c) == '\t') || ((c) == ' ') || ((c) == '\n'))
+#define member(c, s) (index (s, c) != NULL)
+
+#define COMMAND_PREFIX '@'
+
+/* Stuff for splitting large files. */
+#define SPLIT_SIZE_THRESHOLD 70000     /* What's good enough for Stallman... */
+#define DEFAULT_SPLIT_SIZE 50000/* Is probably good enough for me. */
+boolean splitting = true;      /* Always true for now. */
+
+typedef int FUNCTION ();       /* So I can say FUNCTION *foo; */
+
+\f
+/* **************************************************************** */
+/*                                                                 */
+/*                         Global Variables                        */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Global pointer to argv[0]. */
+char *progname;
+
+/* The current input file state. */
+char *input_filename;
+char *input_text;
+int size_of_input_text;
+int input_text_offset;
+int line_number;
+
+#define curchar() input_text[input_text_offset]
+
+#define command_char(c) ((!whitespace(c)) && \
+                         ((c) != '\n') && \
+                         ((c) != '{'))
+#define skip_whitespace() while (input_text_offset != size_of_input_text \
+                                && whitespace(curchar()))\
+  input_text_offset++
+
+/* And writing to the output. */
+
+/* The output file name. */
+char *output_filename, *pretty_output_filename;
+
+/* Current output stream. */
+FILE *output_stream;
+
+/* Position in the output file. */
+int output_position;
+
+/* Output paragraph buffer. */
+char *output_paragraph;
+
+/* Offset into OUTPUT_PARAGRAPH. */
+int output_paragraph_offset;
+
+/* The output paragraph "cursor" horizontal position. */
+int output_column = 0;
+
+/* non-zero means output_paragraph contains text. */
+boolean paragraph_is_open = false;
+
+#define INITIAL_PARAGRAPH_SPACE 5000
+int paragraph_buffer_len = INITIAL_PARAGRAPH_SPACE;
+
+/* Filling.. */
+/* True indicates that filling will take place on long lines. */
+boolean filling_enabled = true;
+
+/* Non-zero means that words are not to be split, even in long lines.  This
+   gets changed for cm_w (). */
+int non_splitting_words = 0;
+
+/* True indicates that filling a line also indents the new line. */
+boolean indented_fill = false;
+
+/* The column at which long lines are broken. */
+int fill_column = 72;
+
+/* The amount of indentation to apply at the start of each line. */
+int current_indent = 0;
+
+/* The amount of indentation to add at the starts of paragraphs.
+   0 means don't change existing indentation at paragraph starts.
+   > 0 is amount to indent new paragraphs by.
+   < 0 means indent to column zero by removing indentation if necessary.
+
+   This is normally zero, but some people prefer paragraph starts to be
+   somewhat more indented than paragraph bodies.  A pretty value for
+   this is 3. */
+int paragraph_start_indent = 3;
+
+/* Non-zero means that the use of paragraph_start_indent is inhibited.
+   @example uses this to line up the left columns of the example text. */
+int inhibit_paragraph_indentation = 0;
+
+/* Indentation that is pending insertion.  We have this for hacking lines
+   which look blank, but contain whitespace.  We want to treat those as
+   blank lines. */
+int pending_indent = 0;
+
+/* The amount that indentation increases/decreases by. */
+int default_indentation_increment = 5;
+
+/* True indicates that indentation is temporarily turned off. */
+boolean no_indent = true;
+
+/* Command name in the process of being hacked. */
+char *command;
+
+/* The index in our internal command table of the currently
+   executing command. */
+int command_index;
+
+/* A stack of file information records.  If a new file is read in with
+   "@input", we remember the old input file state on this stack. */
+typedef struct fstack
+{
+  struct fstack *next;
+  char *filename;
+  char *text;
+  int size;
+  int offset;
+  int line_number;
+} FSTACK;
+
+FSTACK *filestack = (FSTACK *) NULL;
+
+/* Stuff for nodes. */
+/* The current nodes node name */
+char *current_node;
+
+/* The current nodes section level. */
+int current_section = 0;
+
+/* The filename of the current input file.  This is never freed. */
+char *node_filename = (char *)NULL;
+
+/* What we remember for each node. */
+typedef struct tentry
+{
+  struct tentry *next_ent;
+  char *node;          /* name of this node. */
+  char *prev;          /* name of "Prev:" for this node. */
+  char *next;          /* name of "Next:" for this node. */
+  char *up;            /* name of "Up:" for this node.   */
+  int position;                /* output file position of this node. */
+  int line_no;         /* defining line in source file. */
+  char *filename;      /* The file that this node was found in. */
+  int touched;         /* non-zero means this node has been referenced. */
+  int flags;           /* Room for growth.  Right now, contains 1 bit. */
+} TAG_ENTRY;
+
+/* If node-a has a "Next" for node-b, but node-b has no "Prev" for node-a,
+   we turn on this flag bit in node-b's tag entry.  This means that when
+   it is time to validate node-b, we don't report an additional error
+   if there was no "Prev" field. */
+#define PREV_ERROR 0x1
+#define NEXT_ERROR 0x2
+#define UP_ERROR   0x4
+#define NO_WARN           0x8
+
+TAG_ENTRY *tag_table = (TAG_ENTRY *) NULL;
+
+/* Menu reference, *note reference, and validation hacking. */
+
+/* The various references that we know about. */
+enum reftype
+{
+  menu_reference, followed_reference
+};
+
+/* A structure to remember references with.  A reference to a node is
+   either an entry in a menu, or a cross-reference made with [px]ref. */
+typedef struct node_ref
+{
+  struct node_ref *next;
+  char *node;                  /* Name of node referred to. */
+  char *containing_node;       /* Name of node containing this reference. */
+  int line_no;                 /* Line number where the reference occurs. */
+  int section;                 /* Section level where the reference occurs. */
+  char *filename;              /* Name of file where the reference occurs. */
+  enum reftype type;           /* Type of reference, either menu or note. */
+} NODE_REF;
+
+/* The linked list of such structures. */
+NODE_REF *node_references = (NODE_REF *) NULL;
+
+/* Flag which tells us whether to examine menu lines or not. */
+int in_menu = 0;
+
+/* Flags controlling the operation of the program. */
+
+/* Default is to notify users of bad choices. */
+boolean print_warnings = true;
+
+/* Default is to check node references. */
+boolean validating = true;
+
+/* Number of errors that we tolerate on a given fileset. */
+int max_error_level = 100;
+
+/* Maximum number of references to a single node before complaining. */
+int reference_warning_limit = 1000;
+
+/* Non-zero means print out information about what is going on when it
+   is going on. */
+int verbose_mode = 0;
+
+/* The list of commands that we hack in texinfo.  Each one
+   has an associated function.  When the command is encountered in the
+   text, the associated function is called with START as the argument.
+   If the function expects arguments in braces, it remembers itself on
+   the stack.  When the corresponding close brace is encountered, the
+   function is called with END as the argument. */
+
+#define START 0
+#define END 1
+
+typedef struct brace_element
+{
+  struct brace_element *next;
+  FUNCTION *proc;
+  int pos, line;
+} BRACE_ELEMENT;
+
+BRACE_ELEMENT *brace_stack = (BRACE_ELEMENT *) NULL;
+
+/* Forward declarations. */
+
+int
+insert_self (), cm_tex (), cm_asterisk (), cm_dots (), cm_bullet (),
+cm_TeX (), cm_copyright (), cm_code (), cm_samp (), cm_file (), cm_kbd (),
+cm_key (), cm_ctrl (), cm_var (), cm_dfn (), cm_emph (), cm_strong (),
+cm_cite (), cm_italic (), cm_bold (), cm_roman (), cm_title (), cm_w (),
+cm_refill ();
+
+int
+cm_chapter (), cm_unnumbered (), cm_appendix (),
+cm_section (), cm_unnumberedsec (), cm_appendixsec (),
+cm_subsection (), cm_unnumberedsubsec (), cm_appendixsubsec (),
+cm_subsubsection (), cm_unnumberedsubsubsec (), cm_appendixsubsubsec (),
+cm_heading (), cm_chapheading (), cm_subheading (), cm_subsubheading (),
+cm_majorheading ();
+
+/* All @defxxx commands map to cm_defun (). */
+int
+cm_defun ();
+
+int
+cm_node (), cm_menu (), cm_xref (),
+cm_pxref (), cm_inforef (), cm_quotation (), cm_display (), cm_itemize (),
+cm_enumerate (), cm_table (), cm_itemx (), cm_noindent (), cm_setfilename (),
+cm_comment (), cm_ignore (), cm_br (), cm_sp (), cm_page (), cm_group (),
+cm_need (), cm_center (), cm_include (), cm_bye (), cm_item (), cm_end (),
+cm_infoinclude (), cm_ifinfo (), cm_iftex (), cm_titlepage (),
+cm_titlespec (),cm_kindex (), cm_cindex (), cm_findex (), cm_pindex (),
+cm_vindex (), cm_tindex (), cm_asis (), cm_synindex (), cm_settitle (),
+cm_setchapternewpage (), cm_printindex (), cm_minus (), cm_footnote (),
+cm_force_abbreviated_whitespace (), cm_force_sentence_end (), cm_example (),
+cm_smallexample (), cm_lisp (), cm_format (), cm_exdent (), cm_defindex (),
+cm_defcodeindex (), cm_sc (), cm_result (), cm_expansion (), cm_equiv (),
+cm_print (), cm_error (), cm_point (), cm_smallbook ();
+
+int do_nothing ();
+int misplaced_brace (), cm_obsolete ();
+
+typedef struct
+{
+  char *name;
+  FUNCTION *proc;
+  boolean argument_in_braces;
+} COMMAND;
+
+/* Stuff for defining commands on the fly. */
+COMMAND **user_command_array = (COMMAND **) NULL;
+int user_command_array_len = 0;
+
+static COMMAND CommandTable[] = {
+  {"!", cm_force_sentence_end, false},
+  {"'", insert_self, false},
+  {"*", cm_asterisk, false},
+  {".", cm_force_sentence_end, false},
+  {":", cm_force_abbreviated_whitespace, false},
+  {"?", cm_force_sentence_end, false},
+  {"@", insert_self, false},
+  {" ", insert_self, false},
+  {"\n", insert_self, false},
+  {"TeX", cm_TeX, true},
+  {"`", insert_self, false},
+  {"appendix", cm_appendix, false},
+  {"appendixsec", cm_appendixsec, false},
+  {"appendixsubsec", cm_appendixsubsec, false},
+  {"appendixsubsubsec", cm_appendixsubsubsec, false},
+  {"asis", cm_asis, true},
+  {"b", cm_bold, true},
+  {"br", cm_br, false},
+  {"bullet", cm_bullet, true},
+  {"bye", cm_bye, false},
+  {"c", cm_comment, false},
+  {"center", cm_center, false},
+  {"chapheading", cm_chapheading, false},
+  {"chapter", cm_chapter, false},
+  {"cindex", cm_cindex, false},
+  {"cite", cm_cite, true},
+  {"code", cm_code, true},
+  {"comment", cm_comment, false},
+  {"contents", do_nothing, false},
+  {"copyright", cm_copyright, true},
+  {"ctrl", cm_ctrl, true},
+  {"defcodeindex", cm_defcodeindex, false},
+  {"defindex", cm_defindex, false},
+  {"dfn", cm_dfn, true},
+
+/* The `def' commands. */
+  {"defun", cm_defun, false},
+  {"defunx", cm_defun, false},
+  {"defvar", cm_defun, false},
+  {"defvarx", cm_defun, false},
+  {"defopt", cm_defun, false},
+  {"defoptx", cm_defun, false},
+  {"deffn", cm_defun, false},
+  {"deffnx", cm_defun, false},
+  {"defspec", cm_defun, false},
+  {"defspecx", cm_defun, false},
+  {"defmac", cm_defun, false},
+  {"defmacx", cm_defun, false},
+/* The end of the `def' commands. */
+
+  {"display", cm_display, false},
+  {"dots", cm_dots, true},
+  {"emph", cm_emph, true},
+  {"end", cm_end, false},
+  {"enumerate", cm_enumerate, false},
+  {"equiv", cm_equiv, true},
+  {"error", cm_error, true},
+  {"example", cm_example, false},
+  {"exdent", cm_exdent, false},
+  {"expansion", cm_expansion, true},
+  {"file", cm_file, true},
+  {"findex", cm_findex, false},
+  {"format", cm_format, false},
+  {"group", cm_group, false},
+  {"heading", cm_heading, false},
+  {"i", cm_italic, true},
+  {"iappendix", cm_appendix, false},
+  {"iappendixsec", cm_appendixsec, false},
+  {"iappendixsubsec", cm_appendixsubsec, false},
+  {"iappendixsubsubsec", cm_appendixsubsubsec, false},
+  {"ichapter", cm_chapter, false},
+  {"ifinfo", cm_ifinfo, false},
+  {"iftex", cm_iftex, false},
+  {"ignore", cm_ignore, false},
+  {"include", cm_include, false},
+  {"inforef", cm_inforef, true},
+  {"input", cm_include, false},
+  {"isection", cm_section, false},
+  {"isubsection", cm_subsection, false},
+  {"isubsubsection", cm_subsubsection, false},
+  {"item", cm_item, false},
+  {"itemize", cm_itemize, false},
+  {"itemx", cm_itemx, false},
+  {"iunnumbered", cm_unnumbered, false},
+  {"iunnumberedsec", cm_unnumberedsec, false},
+  {"iunnumberedsubsec", cm_unnumberedsubsec, false},
+  {"iunnumberedsubsubsec", cm_unnumberedsubsubsec, false},
+  {"kbd", cm_kbd, true},
+  {"key", cm_key, true},
+  {"kindex", cm_kindex, false},
+  {"lisp", cm_lisp, false},
+  {"majorheading", cm_majorheading, false},
+  {"menu", cm_menu},
+  {"minus", cm_minus, true},
+  {"need", cm_need, false},
+  {"node", cm_node, false},
+  {"noindent", cm_noindent, false},
+  {"page", do_nothing, false},
+  {"pindex", cm_pindex, false},
+  {"point", cm_point, true},
+  {"print", cm_print, true},
+  {"printindex", cm_printindex, false},
+  {"pxref", cm_pxref, true},
+  {"quotation", cm_quotation, false},
+  {"r", cm_roman, true},
+  {"ref", cm_xref, true},
+  {"refill", cm_refill, false},
+  {"result", cm_result, true},
+  {"samp", cm_samp, true},
+  {"sc", cm_sc, true},
+  {"section", cm_section, false},
+  {"setchapternewpage", cm_setchapternewpage, false},
+  {"setfilename", cm_setfilename, false},
+  {"settitle", cm_settitle, false},
+  {"smallexample", cm_smallexample, false},
+  {"smallbook", cm_smallbook, false},
+  {"sp", cm_sp, false},
+  {"strong", cm_strong, true},
+  {"subheading", cm_subheading, false},
+  {"subsection", cm_subsection, false},
+  {"subsubheading", cm_subsubheading, false},
+  {"subsubsection", cm_subsubsection, false},
+  {"summarycontents", do_nothing, false},
+  {"syncodeindex", cm_synindex, false},
+  {"synindex", cm_synindex, false},
+  {"t", cm_title, true},
+  {"table", cm_table, false},
+  {"tex", cm_tex, false},
+  {"tindex", cm_tindex, false},
+  {"titlepage", cm_titlepage, false},
+  {"titlespec", cm_titlespec, false},
+  {"unnumbered", cm_unnumbered, false},
+  {"unnumberedsec", cm_unnumberedsec, false},
+  {"unnumberedsubsec", cm_unnumberedsubsec, false},
+  {"unnumberedsubsubsec", cm_unnumberedsubsubsec, false},
+  {"var", cm_var, true},
+  {"vindex", cm_vindex, false},
+  {"w", cm_w, true},
+  {"xref", cm_xref, true},
+  {"{", insert_self, false},
+  {"}", insert_self, false},
+
+  /* Now @include does what this was supposed to. */
+  {"infoinclude", cm_infoinclude, false},
+  {"footnote", cm_footnote, false}, /* self-arg eater */
+
+  {(char *) NULL, (FUNCTION *) NULL}, false};
+
+/* Non-zero means we are running inside of Emacs. */
+int in_emacs = 0;
+
+#ifndef MAKEINFO_MAJOR
+#define MAKEINFO_MAJOR 1
+#endif
+
+#ifndef MAKEINFO_MINOR
+#define MAKEINFO_MINOR 0
+#endif
+
+int major_version = MAKEINFO_MAJOR;
+int minor_version = MAKEINFO_MINOR;
+
+struct option long_options[] =
+{
+  { "no-validate", 0, &validating, false },    /* formerly -nv */
+  { "no-warn", 0, &print_warnings, false },    /* formerly -nw */
+  { "no-split", 0, &splitting, false },                /* formerly -ns */
+  { "verbose", 0, &verbose_mode, 1 },          /* formerly -verbose */
+  { "fill-column", 1, 0, 'f' },                        /* formerly -fc */
+  { "paragraph-indent", 1, 0, 'p' },           /* formerly -pi */
+  { "error-limit", 1, 0, 'e' },                        /* formerly -el */
+  { "reference-limit", 1, 0, 'r' },            /* formerly -rl */
+  { "footnote-style", 1, 0, 's' },             /* formerly -ft */
+  { "version", 0, 0, 'V' },
+  {NULL, 0, NULL, 0}
+};
+  
+/* **************************************************************** */
+/*                                                                 */
+/*                     Main ()  Start of code                      */
+/*                                                                 */
+/* **************************************************************** */
+
+/* For each file mentioned in the command line, process it, turning
+   texinfo commands into wonderfully formatted output text. */
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  char *t = (char *) getenv ("EMACS");
+  int c;
+  int ind;
+
+  progname = argv[0];
+
+  if (t && strcmp (t, "t") == 0)
+    in_emacs++;
+
+  /* Parse argument flags from the input line. */
+  while ((c = getopt_long (argc, argv, "", long_options, &ind)) != EOF)
+    {
+      if (c == 0 && long_options[ind].flag == 0)
+       c = long_options[ind].val;
+      switch (c)
+       {
+       case 'f':
+         /* user specified fill_column? */
+         if (sscanf (optarg, "%d", &fill_column) != 1)
+           usage ();
+         break;
+
+       case 'p':
+         /* User specified paragraph indent (paragraph_start_index)? */
+         if (sscanf (optarg, "%d", &paragraph_start_indent) != 1)
+           usage ();
+         break;
+
+       case 'e':
+         /* User specified error level? */
+         if (sscanf (optarg, "%d", &max_error_level) != 1)
+           usage ();
+         break;
+
+       case 'r':
+         /* User specified reference warning limit? */
+         if (sscanf (optarg, "%d", &reference_warning_limit) != 1)
+           usage ();
+         break;
+
+       case 's':
+         /* User specified footnote style? */
+         set_footnote_style (optarg);
+         break;
+
+       case 'V':               /* Use requested version info? */
+         fprintf (stderr, "Makeinfo verison %d.%d.\n",
+                  major_version, minor_version);
+         exit (NO_ERROR);
+         break;
+
+       case '?':
+         usage ();
+       }
+    }
+
+  if (optind == argc)
+    usage ();
+
+  /* Remaining arguments are file names of texinfo files.
+     Convert them, one by one. */
+  while (optind != argc)
+    convert (argv[optind++]);
+
+  exit (NO_ERROR);
+}
+
+
+/* **************************************************************** */
+/*                                                                 */
+/*                     Generic Utilities                           */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Just like malloc, but kills the program in case of fatal error. */
+char *
+xmalloc (nbytes)
+     int nbytes;
+{
+  char *temp = (char *) malloc (nbytes);
+  if (temp == (char *) NULL)
+    {
+      error ("Virtual memory exhausted! Needed %d bytes.", nbytes);
+      exit (FATAL);
+    }
+  return (temp);
+}
+
+/* Like realloc (), but barfs if there isn't enough memory. */
+char *
+xrealloc (pointer, nbytes)
+     char *pointer;
+     int nbytes;
+{
+  pointer = (char *) realloc (pointer, nbytes);
+  if (!pointer)
+    {
+      error ("Virtual memory exhausted in realloc ().");
+      abort ();
+    }
+  return (pointer);
+}
+
+/* Tell the user how to use this program. */
+usage ()
+{
+  fprintf (stderr, "Usage: %s [options] texinfo-file...\n\
+\n\
+This program accepts as input files of texinfo commands and text\n\
+and outputs a file suitable for reading with GNU Info.\n\
+\n\
+The options are:\n\
+`+no-validate' to suppress node cross reference validation.\n\
+`+no-warn' to suppress warning messages (errors are still output).\n\
+`+no-split' to suppress the splitting of large files.\n\
+`+verbose' to print information about what is being done.\n\
+`+version' to print the version number of Makeinfo.\n\
+`+paragraph-indent NUM' to set the paragraph indent to NUM (default %d).\n\
+`+fill-column NUM' to set the filling column to NUM (default %d).\n\
+`+error-limit NUM' to set the error limit to NUM (default %d).\n\
+`+reference-limit NUM' to set the reference warning limit to NUM (default %d).\n\
+`+footnote-style STYLE' to set the footnote style to STYLE.  STYLE should\n\
+  either be `MN' for `make node', or `BN' for `bottom node'.\n\n",
+          progname, paragraph_start_indent,
+          fill_column, max_error_level, reference_warning_limit);
+  exit (FATAL);
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*                     Manipulating Lists                          */
+/*                                                                 */
+/* **************************************************************** */
+
+typedef struct generic_list
+{
+  struct generic_list *next;
+}            GENERIC_LIST;
+
+/* Reverse the chain of structures in LIST.  Output the new head
+   of the chain.  You should always assign the output value of this
+   function to something, or you will lose the chain. */
+GENERIC_LIST *
+reverse_list (list)
+     register GENERIC_LIST *list;
+{
+  register GENERIC_LIST *next;
+  register GENERIC_LIST *prev = (GENERIC_LIST *) NULL;
+
+  while (list)
+    {
+      next = list->next;
+      list->next = prev;
+      prev = list;
+      list = next;
+    }
+  return (prev);
+}
+
+
+/* **************************************************************** */
+/*                                                                 */
+/*                     Pushing and Popping Files                   */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Find and load the file named FILENAME.  Return a pointer to
+   the loaded file, or NULL if it can't be loaded. */
+char *
+find_and_load (filename)
+     char *filename;
+{
+  struct stat fileinfo;
+  int file, n, i, count = 0;
+  char *result = (char *) NULL;
+
+  if (stat (filename, &fileinfo) != 0)
+    goto error_exit;
+
+  file = open (filename, O_RDONLY);
+  if (file < 0)
+    goto error_exit;
+
+  /* Load the file. */
+  result = xmalloc (fileinfo.st_size);
+
+  /* VMS stat lies about the st_size value.  The actual number of
+     readable bytes is always less than this value.  The arcane
+     mysteries of VMS/RMS are too much to probe, so this hack
+    suffices to make things work. */
+#if defined (VMS)
+  while ((n = read (file, result+count, fileinfo.st_size)) > 0)
+    count += n;
+  if (n == -1)
+#else
+    count = fileinfo.st_size;
+    if (read (file, result, fileinfo.st_size) != fileinfo.st_size)
+#endif
+  error_exit:
+    {
+      if (result)
+       free (result);
+      if (file != -1)
+       close (file);
+      return ((char *) NULL);
+    }
+  close (file);
+
+  /* Set the globals to the new file. */
+  input_text = result;
+  size_of_input_text = fileinfo.st_size;
+  input_filename = savestring (filename);
+  node_filename = savestring (filename);
+  input_text_offset = 0;
+  line_number = 1;
+  return (result);
+}
+
+/* Save the state of the current input file. */
+pushfile ()
+{
+  FSTACK *newstack = (FSTACK *) xmalloc (sizeof (FSTACK));
+  newstack->filename = input_filename;
+  newstack->text = input_text;
+  newstack->size = size_of_input_text;
+  newstack->offset = input_text_offset;
+  newstack->line_number = line_number;
+  newstack->next = filestack;
+
+  filestack = newstack;
+  push_node_filename ();
+}
+
+/* Make the current file globals be what is on top of the file stack. */
+popfile ()
+{
+  extern int executing_string;
+  FSTACK *temp = filestack;
+
+  if (!filestack)
+    abort ();                  /* My fault.  I wonder what I did? */
+
+  /* Make sure that commands with braces have been satisfied. */
+  if (!executing_string)
+    discard_braces ();
+
+  /* Get the top of the stack into the globals. */
+  input_filename = filestack->filename;
+  input_text = filestack->text;
+  size_of_input_text = filestack->size;
+  input_text_offset = filestack->offset;
+  line_number = filestack->line_number;
+
+  /* Pop the stack. */
+  filestack = filestack->next;
+  free (temp);
+  pop_node_filename ();
+}
+
+/* Flush all open files on the file stack. */
+flush_file_stack ()
+{
+  while (filestack)
+    {
+      free (input_filename);
+      free (input_text);
+      popfile ();
+    }
+}
+
+int node_filename_stack_index = 0;
+int node_filename_stack_size = 0;
+char **node_filename_stack = (char **)NULL;
+
+push_node_filename ()
+{
+  if (node_filename_stack_index + 1 > node_filename_stack_size)
+    {
+      if (!node_filename_stack)
+       node_filename_stack =
+         (char **)xmalloc ((node_filename_stack_size += 10)
+                           * sizeof (char *));
+      else
+       node_filename_stack =
+         (char **)xrealloc (node_filename_stack,
+                            (node_filename_stack_size + 10)
+                            * sizeof (char *));
+    }
+
+  node_filename_stack[node_filename_stack_index] = node_filename;
+  node_filename_stack_index++;
+}
+
+pop_node_filename ()
+{
+  node_filename = node_filename_stack[--node_filename_stack_index];
+}
+
+/* Return just the simple part of the filename; i.e. the
+   filename without the path information, or extensions.
+   This conses up a new string. */
+char *
+filename_part (filename)
+     char *filename;
+{
+  register int i = strlen (filename) - 1;
+
+  while (i && filename[i] != '/')
+    i--;
+  if (filename[i] == '/')
+    i++;
+
+#ifdef REMOVE_OUTPUT_EXTENSIONS
+  result = savestring (&filename[i]);
+
+  /* See if there is an extension to remove.  If so, remove it. */
+  if (rindex (result, '.'))
+    *(rindex (result, '.')) = '\0';
+  return (result);
+#else
+  return (savestring (&filename[i]));
+#endif /* REMOVE_OUTPUT_EXTENSIONS */
+}
+
+/* Return the pathname part of filename.  This can be NULL. */
+char *
+pathname_part (filename)
+     char *filename;
+{
+  char *expand_filename ();
+  char *result = (char *) NULL;
+  register int i;
+
+  filename = expand_filename (filename, "");
+
+  i = strlen (filename) - 1;
+
+  while (i && filename[i] != '/')
+    i--;
+  if (filename[i] == '/')
+    i++;
+
+  if (i)
+    {
+      result = xmalloc (1 + i);
+      strncpy (result, filename, i);
+      result[i] = '\0';
+    }
+  free (filename);
+  return (result);
+}
+
+/* Return the expansion of FILENAME. */
+char *
+expand_filename (filename, input_name)
+     char *filename, *input_name;
+{
+  char *full_pathname ();
+  filename = full_pathname (filename);
+
+  if (filename[0] == '.')
+    return (filename);
+
+  if (filename[0] != '/' && input_name[0] == '/')
+    {
+      /* Make it so that relative names work. */
+      char *result = xmalloc (1 + strlen (input_name)
+                             + strlen (filename));
+      int i = strlen (input_name) - 1;
+
+      strcpy (result, input_name);
+      while (result[i] != '/' && i)
+       i--;
+      if (result[i] == '/')
+       i++;
+      strcpy (&result[i], filename);
+      free (filename);
+      return (result);
+    }
+  return (filename);
+}
+
+/* Return the full path to FILENAME. */
+char *
+full_pathname (filename)
+     char *filename;
+{
+  int initial_character;
+
+  if (filename && (initial_character = *filename))
+    {
+      if (initial_character == '/')
+       return (savestring (filename));
+      if (initial_character != '~')
+       {
+         return (savestring (filename));
+       }
+      else
+       {
+         if (filename[1] == '/')
+           {
+             /* Return the concatenation of HOME and the rest of the string. */
+             char *temp_home = (char *) getenv ("HOME");
+             char *temp_name = xmalloc (strlen (&filename[2])
+                                        + 1
+                                        + temp_home ? strlen (temp_home)
+                                        : 0);
+             if (temp_home)
+               strcpy (temp_name, temp_home);
+             strcat (temp_name, &filename[2]);
+             return (temp_name);
+           }
+         else
+           {
+             struct passwd *user_entry;
+             int i, c;
+             char *username = xmalloc (257);
+             char *temp_name;
+
+             for (i = 1; c = filename[i]; i++)
+               {
+                 if (c == '/')
+                   break;
+                 else
+                   username[i - 1] = c;
+               }
+             if (c)
+               username[i - 1] = '\0';
+
+             user_entry = getpwnam (username);
+
+             if (!user_entry)
+               return (savestring (filename));
+
+             temp_name = xmalloc (1 + strlen (user_entry->pw_dir)
+                                  + strlen (&filename[i]));
+             strcpy (temp_name, user_entry->pw_dir);
+             strcat (temp_name, &filename[i]);
+             return (temp_name);
+           }
+       }
+    }
+  else
+    {
+      return (savestring (filename));
+    }
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*                     Error Handling                              */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Number of errors encountered. */
+int errors_printed = 0;
+
+/* Print the last error gotten from the file system. */
+fs_error (filename)
+     char *filename;
+{
+  perror (filename);
+  return ((int) false);
+}
+
+/* Print an error message, and return false. */
+error (format, arg1, arg2, arg3, arg4, arg5)
+     char *format;
+{
+  remember_error ();
+  fprintf (stderr, format, arg1, arg2, arg3, arg4, arg5);
+  fprintf (stderr, "\n");
+  return ((int) false);
+}
+
+/* Just like error (), but print the line number as well. */
+line_error (format, arg1, arg2, arg3, arg4, arg5)
+     char *format;
+{
+  remember_error ();
+  fprintf (stderr, "%s:%d: ", input_filename, line_number);
+  fprintf (stderr, format, arg1, arg2, arg3, arg4, arg5);
+  fprintf (stderr, ".\n");
+  return ((int) false);
+}
+
+warning (format, arg1, arg2, arg3, arg4, arg5)
+     char *format;
+{
+  if (print_warnings)
+    {
+      fprintf (stderr, "%s:%d: Warning: ", input_filename, line_number);
+      fprintf (stderr, format, arg1, arg2, arg3, arg4, arg5);
+      fprintf (stderr, ".\n");
+    }
+  return ((int) false);
+}
+
+/* Remember that an error has been printed.  If this is the first
+   error printed, then tell them which program is printing them.
+   If more than max_error_level have been printed, then exit the
+   program. */
+remember_error ()
+{
+  errors_printed++;
+  if (max_error_level && (errors_printed > max_error_level))
+    {
+      fprintf (stderr, "Too many errors!  Gave up.");
+      flush_file_stack ();
+      cm_bye ();
+    }
+}
+
+\f
+/* **************************************************************** */
+/*                                                                 */
+/*                     Hacking Tokens and Strings                  */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Return the next token as a string pointer.  We cons the
+   string. */
+char *
+read_token ()
+{
+  int i, character;
+  char *result;
+
+  /* Hack special case.  If the first character to be read is
+     self-delimiting, then that is the command itself. */
+
+  character = curchar ();
+  if (self_delimiting (character))
+    {
+      input_text_offset++;
+      result = savestring (" ");
+      *result = character;
+      return (result);
+    }
+
+  for (i = 0; ((input_text_offset != size_of_input_text)
+              && (character = curchar ())
+              && command_char (character));
+       i++, input_text_offset++);
+  result = xmalloc (i + 1);
+  strncpy (result, &input_text[input_text_offset - i], i);
+  result[i] = '\0';
+  return (result);
+}
+
+/* Return TRUE if CHARACTER is self-delimiting. */
+boolean
+self_delimiting (character)
+     int character;
+{
+  return (member (character, "{}:.@*'`,!?; \n"));
+}
+
+/* Clear whitespace from the front and end of string. */
+canon_white (string)
+     char *string;
+{
+  int len = strlen (string);
+  int x;
+
+  if (!len)
+    return;
+
+  for (x = 0; x < len; x++)
+    {
+      if (!whitespace (string[x]))
+       {
+         strcpy (string, string + x);
+         break;
+       }
+    }
+  len = strlen (string);
+  if (len)
+    len--;
+  while (len > -1 && cr_or_whitespace (string[len]))
+    len--;
+  string[len + 1] = '\0';
+}
+
+/* Bash STRING, replacing all whitespace with just one space. */
+fix_whitespace (string)
+     char *string;
+{
+  char *temp = xmalloc (strlen (string) + 1);
+  int string_index = 0;
+  int temp_index = 0;
+  int c;
+
+  canon_white (string);
+
+  while (string[string_index])
+    {
+      c = temp[temp_index++] = string[string_index++];
+
+      if (c == ' ' || c == '\n' || c == '\t')
+       {
+         temp[temp_index - 1] = ' ';
+         while ((c = string[string_index]) && (c == ' ' ||
+                                               c == '\t' ||
+                                               c == '\n'))
+           string_index++;
+       }
+    }
+  temp[temp_index] = '\0';
+  strcpy (string, temp);
+  free (temp);
+}
+
+/* Discard text until the desired string is found.  The string is
+   included in the discarded text. */
+discard_until (string)
+     char *string;
+{
+  int temp = search_forward (string, input_text_offset);
+
+  int tt = (temp < 0) ? size_of_input_text : temp + strlen (string);
+  int from = input_text_offset;
+
+  /* Find out what line we are on. */
+  while (from != tt)
+    if (input_text[from++] == '\n')
+      line_number++;
+
+  if (temp < 0)
+    {
+      input_text_offset = size_of_input_text - strlen (string);
+
+      if (strcmp (string, "\n") != 0)
+       {
+         line_error ("Expected `%s'", string);
+         return;
+       }
+    }
+  else
+    input_text_offset = temp;
+
+  input_text_offset += strlen (string);
+}
+
+/* Read characters from the file until we are at MATCH.
+   Place the characters read into STRING.
+   On exit input_text_offset is after the match string.
+   Return the length of STRING. */
+get_until (match, string)
+     char *match, **string;
+{
+  int len;
+  int current_point = input_text_offset;
+  int x = current_point;
+  int new_point = search_forward (match, input_text_offset);
+
+  if (new_point < 0)
+    new_point = size_of_input_text;
+  len = new_point - current_point;
+
+  /* Keep track of which line number we are at. */
+  while (x != new_point)
+    if (input_text[x++] == '\n')
+      line_number++;
+
+  *string = xmalloc (len + 1);
+
+  strncpy (*string, &input_text[current_point], len);
+  (*string)[len] = '\0';
+
+  /* Now leave input_text_offset in a consistent state. */
+  input_text_offset = new_point + (strlen (match) - 1);
+  if (input_text_offset > size_of_input_text)
+    input_text_offset = size_of_input_text;
+}
+
+/* Read characters from the file until we are at MATCH or end of line.
+   Place the characters read into STRING.  */
+get_until_in_line (match, string)
+     char *match, **string;
+{
+  int real_bottom = size_of_input_text;
+  int temp = search_forward ("\n", input_text_offset);
+  if (temp < 0)
+    temp = size_of_input_text;
+  size_of_input_text = temp;
+  get_until (match, string);
+  size_of_input_text = real_bottom;
+}
+
+get_rest_of_line (string)
+     char **string;
+{
+  get_until ("\n", string);
+  canon_white (*string);
+  if (curchar () == '\n')
+    {                          /* as opposed to the end of the file... */
+      line_number++;
+      input_text_offset++;
+    }
+}
+
+/* Read characters from the file until we are at MATCH or closing brace.
+   Place the characters read into STRING.  */
+get_until_in_braces (match, string)
+     char *match, **string;
+{
+  int i, brace = 0;
+  int match_len = strlen (match);
+  char *temp;
+
+  for (i = input_text_offset; i < size_of_input_text; i++)
+    {
+      if (input_text[i] == '{')
+       brace++;
+      if (input_text[i] == '}')
+       brace--;
+      if (input_text[i] == '\n')
+       line_number++;
+      if (brace < 0 ||
+         (brace == 0 && strncmp (input_text + i, match, match_len) == 0))
+       break;
+    }
+  match_len = i - input_text_offset;
+  temp = xmalloc (2 + match_len);
+  strncpy (temp, input_text + input_text_offset, match_len);
+  temp[match_len] = '\0';
+  input_text_offset = i;
+  *string = temp;
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*                     Converting the File                         */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Convert the file named by NAME.  The output is saved on the file
+   named as the argument to the @setfilename command. */
+convert (name)
+     char *name;
+{
+  char *real_output_filename, *expand_filename (), *filename_part ();
+  init_tag_table ();
+  init_indices ();
+  init_internals ();
+  init_paragraph ();
+
+  if (!find_and_load (name))
+    {
+      /* For some reason, the file couldn't be loaded.  Print a message
+        to that affect, and split. */
+      fs_error (name);
+      return;
+    }
+  else
+    input_filename = savestring (name);
+
+  /* Search this file looking for the special string which starts conversion.
+     Once found, we may truly begin. */
+
+  input_text_offset = search_forward ("@setfilename", 0);
+  if (input_text_offset < 0)
+    {
+      error ("No `@setfilename' found in `%s'", name);
+      goto finished;
+    }
+  else
+    input_text_offset += strlen ("@setfilename");
+
+  get_until ("\n", &output_filename);  /* no braces expected. */
+  canon_white (output_filename);
+
+  printf ("Making info file `%s' from `%s'.\n", output_filename, name);
+  real_output_filename = expand_filename (output_filename, name);
+  output_stream = fopen (real_output_filename, "w");
+  if (output_stream == NULL)
+    {
+      fs_error (real_output_filename);
+      goto finished;
+    }
+
+  /* Make the displayable filename from output_filename.  Only the root
+     portion of the filename need be displayed. */
+  pretty_output_filename = filename_part (output_filename);
+
+  /* For this file only, count the number of newlines from the top of
+     the file to here.  This way, we keep track of line numbers for
+     error reporting.  Line_number starts at 1, since the user isn't
+     zero-based. */
+  {
+    int temp = 0;
+    line_number = 1;
+    while (temp != input_text_offset)
+      if (input_text[temp++] == '\n')
+       line_number++;
+  }
+
+  add_word_args ("Info file %s, produced by Makeinfo, -*- Text -*-\n\
+from input file %s.\n", output_filename, input_filename);
+  close_paragraph ();
+
+  reader_loop ();
+
+finished:
+  close_paragraph ();
+  flush_file_stack ();
+  if (output_stream != NULL)
+    {
+      output_pending_notes ();
+      free_pending_notes ();
+      if (tag_table != NULL)
+       {
+         tag_table = (TAG_ENTRY *) reverse_list (tag_table);
+         write_tag_table ();
+       }
+
+      fclose (output_stream);
+
+      /* If validating, then validate the entire file right now. */
+      if (validating)
+       validate_file (real_output_filename, tag_table);
+
+      /* This used to test  && !errors_printed.
+        But some files might have legit warnings.  So split anyway.  */
+      if (splitting)
+       split_file (real_output_filename, 0);
+    }
+}
+
+free_and_clear (pointer)
+     char **pointer;
+{
+  if ((*pointer) != (char *) NULL)
+    {
+      free (*pointer);
+      *pointer = (char *) NULL;
+    }
+}
+
+ /* Initialize some state. */
+init_internals ()
+{
+  free_and_clear (&current_node);
+  free_and_clear (&output_filename);
+  free_and_clear (&command);
+  free_and_clear (&input_filename);
+  free_node_references ();
+  init_insertion_stack ();
+  init_brace_stack ();
+  command_index = 0;
+  in_menu = 0;
+}
+
+init_paragraph ()
+{
+  free_and_clear (&output_paragraph);
+  output_paragraph = xmalloc (paragraph_buffer_len);
+  output_position = 0;
+  output_paragraph[0] = '\0';
+  output_paragraph_offset = 0;
+  output_column = 0;
+  paragraph_is_open = false;
+  current_indent = 0;
+}
+
+/* Okay, we are ready to start the conversion.  Call the reader on
+   some text, and fill the text as it is output.  Handle commands by
+   remembering things like open braces and the current file position on a
+   stack, and when the corresponding close brace is found, you can call
+   the function with the proper arguments. */
+reader_loop ()
+{
+  int character;
+  boolean done = false;
+  int dash_count = 0;
+
+  while (!done)
+    {
+      if (input_text_offset >= size_of_input_text)
+       {
+         if (filestack)
+           {
+             free (input_filename);
+             free (input_text);
+             popfile ();
+           }
+         else
+           break;
+       }
+      character = curchar ();
+
+      if (!in_fixed_width_font &&
+         (character == '\'' || character == '`') &&
+         input_text[input_text_offset + 1] == character)
+       {
+         input_text_offset++;
+         character = '"';
+       }
+
+      if (character == '-')
+       {
+         dash_count++;
+         if (dash_count == 3 && !in_fixed_width_font)
+           {
+             input_text_offset++;
+             continue;
+           }
+       }
+      else
+       {
+         dash_count = 0;
+       }
+
+      if (character == '\n')
+       {
+         line_number++;
+         if (in_menu && input_text_offset + 1 < size_of_input_text)
+           {
+             glean_node_from_menu ();
+           }
+
+         /* If the following line is all whitespace, advance to the carriage
+            return on it. */
+         {
+           register int i = input_text_offset + 1;
+           
+           while (i < size_of_input_text && whitespace (input_text[i]))
+             i++;
+           
+               if (i == size_of_input_text || input_text[i] == '\n')
+                 input_text_offset = i - 1;
+         }
+       }
+      
+      switch (character)
+       {
+       case COMMAND_PREFIX:
+         read_command ();
+         if (strcmp (command, "bye") == 0)
+           {
+             done = true;
+             continue;
+           }
+         break;
+
+       case '{':
+
+         /* Special case.  I'm not supposed to see this character by itself.
+            If I do, it means there is a syntax error in the input text.
+            Report the error here, but remember this brace on the stack so
+            you can ignore its partner. */
+
+         line_error ("Misplaced `{'");
+         remember_brace (misplaced_brace);
+
+         /* Don't advance input_text_offset since this happens in
+            remember_brace ().
+            input_text_offset++;
+           */
+         break;
+
+       case '}':
+         pop_and_call_brace ();
+         input_text_offset++;
+         break;
+
+       default:
+         add_char (character);
+         input_text_offset++;
+       }
+    }
+}
+
+/* Find the command corresponding to STRING.  If the command
+   is found, return a pointer to the data structure.  Otherwise
+   return (-1). */
+COMMAND *
+get_command_entry (string)
+     char *string;
+{
+  register int i;
+
+  for (i = 0; CommandTable[i].name; i++)
+    if (strcmp (CommandTable[i].name, string) == 0)
+      return (&CommandTable[i]);
+
+  /* This command is not in our predefined command table.  Perhaps
+     it is a user defined command. */
+  for (i = 0; i < user_command_array_len; i++)
+    if (user_command_array[i] &&
+       (strcmp (user_command_array[i]->name, string) == 0))
+      return (user_command_array[i]);
+
+  /* Nope, we never heard of this command. */
+  return ((COMMAND *) - 1);
+}
+
+/* input_text_offset is right at the command prefix character.
+   Read the next token to determine what to do. */
+read_command ()
+{
+  COMMAND *entry;
+  input_text_offset++;
+  free_and_clear (&command);
+  command = read_token ();
+
+  entry = get_command_entry (command);
+  if ((int) entry < 0)
+    {
+      line_error ("Unknown info command `%s'", command);
+      return;
+    }
+
+  if (entry->argument_in_braces)
+    remember_brace (entry->proc);
+
+  (*(entry->proc)) (START);
+}
+
+/* Return the string which invokes PROC; a pointer to a function. */
+char *
+find_proc_name (proc)
+     FUNCTION *proc;
+{
+  register int i;
+
+  for (i = 0; CommandTable[i].name; i++)
+    if (proc == CommandTable[i].proc)
+      return (CommandTable[i].name);
+  return ("NO_NAME!");
+}
+
+init_brace_stack ()
+{
+  brace_stack = (BRACE_ELEMENT *) NULL;
+}
+
+remember_brace (proc)
+     FUNCTION *proc;
+{
+  if (curchar () != '{')
+    line_error ("@%s expected `{..}'", command);
+  else
+    input_text_offset++;
+  remember_brace_1 (proc, output_paragraph_offset);
+}
+
+/* Remember the current output position here.  Save PROC
+   along with it so you can call it later. */
+remember_brace_1 (proc, position)
+     FUNCTION *proc;
+     int position;
+{
+  BRACE_ELEMENT *new = (BRACE_ELEMENT *) xmalloc (sizeof (BRACE_ELEMENT));
+  new->next = brace_stack;
+  new->proc = proc;
+  new->pos = position;
+  new->line = line_number;
+  brace_stack = new;
+}
+
+/* Pop the top of the brace stack, and call the associated function
+   with the args END and POS. */
+pop_and_call_brace ()
+{
+  BRACE_ELEMENT *temp;
+  FUNCTION *proc;
+  int pos;
+
+  if (brace_stack == (BRACE_ELEMENT *) NULL)
+    return (line_error ("Unmatched close bracket"));
+
+  pos = brace_stack->pos;
+  proc = brace_stack->proc;
+  temp = brace_stack->next;
+  free (brace_stack);
+  brace_stack = temp;
+
+  return ((*proc) (END, pos, output_paragraph_offset));
+}
+
+/* You call discard_braces () when you shouldn't have any braces on the stack.
+   I used to think that this happens for commands that don't take arguments
+   in braces, but that was wrong because of things like @code{foo @@}.  So now
+   I only detect it at the beginning of nodes. */
+discard_braces ()
+{
+  int temp_line_number = line_number;
+  char *proc_name;
+
+  if (!brace_stack)
+    return;
+
+  while (brace_stack)
+    {
+      line_number = brace_stack->line;
+      proc_name = find_proc_name (brace_stack->proc);
+      line_error ("@%s missing close brace", proc_name);
+      line_number = temp_line_number;
+      pop_and_call_brace ();
+    }
+}
+
+get_char_len (character)
+     int character;
+{
+  /* Return the printed length of the character. */
+  int len;
+
+  switch (character)
+    {
+    case '\t':
+      len = (output_column + 8) & 0xf7;
+      if (len > fill_column)
+       len = fill_column - output_column;
+      else
+       len = len - output_column;
+      break;
+
+    case '\n':
+      len = fill_column - output_column;
+      break;
+
+    default:
+      if (character < ' ')
+       len = 2;
+      else
+       len = 1;
+    }
+  return (len);
+}
+
+add_word_args (format, arg1, arg2, arg3, arg4, arg5)
+     char *format;
+{
+  char buffer[1000];
+  sprintf (buffer, format, arg1, arg2, arg3, arg4, arg5);
+  add_word (buffer);
+}
+
+/* Add STRING to output_paragraph. */
+add_word (string)
+     char *string;
+{
+  while (*string)
+    add_char (*string++);
+}
+
+boolean last_char_was_newline = true;
+int last_inserted_character = 0;
+
+/* Add the character to the current paragraph.  If filling_enabled is
+   true, then do filling as well. */
+add_char (character)
+     int character;
+{
+  extern int must_start_paragraph;
+
+  /* If we are adding a character now, then we don't have to
+     ignore close_paragraph () calls any more. */
+  if (must_start_paragraph)
+    {
+      must_start_paragraph = 0;
+      if (current_indent > output_column)
+       {
+         indent (current_indent - output_column);
+         output_column = current_indent;
+       }
+    }
+
+  if (non_splitting_words && member (character, " \t\n"))
+    character = ' ' | 0x80;
+
+  switch (character)
+    {
+
+    case '\n':
+      if (!filling_enabled)
+       {
+         insert ('\n');
+
+         /* Should I be flushing output here? * /
+          flush_output (); */
+
+         output_column = 0;
+         if (!no_indent)
+           indent (output_column = current_indent);
+         break;
+       }
+      else
+       {
+         if (sentence_ender (last_inserted_character))
+           {
+             insert (' ');
+             output_column++;
+             last_inserted_character = character;
+           }
+       }
+
+      if (last_char_was_newline)
+       {
+         close_paragraph ();
+         pending_indent = 0;
+       }
+      else
+       {
+         last_char_was_newline = true;
+         insert (' ');
+         output_column++;
+       }
+      break;
+
+    default:
+      {
+       int len = get_char_len (character);
+       if ((character == ' ') && (last_char_was_newline))
+         {
+           if (!paragraph_is_open)
+             {
+               pending_indent++;
+               return;
+             }
+         }
+       if (!paragraph_is_open)
+         {
+           start_paragraph ();
+
+           /* If the paragraph is supposed to be indented a certain way,
+              then discard all of the pending whitespace.  Otherwise, we
+              let the whitespace stay. */
+           if (!paragraph_start_indent)
+             indent (pending_indent);
+           pending_indent = 0;
+         }
+       if ((output_column += len) >= fill_column)
+         {
+           if (filling_enabled)
+             {
+               int temp = output_paragraph_offset - 1;
+               while (temp > 0 && output_paragraph[--temp] != '\n')
+                 {
+                   if (output_paragraph[temp] == ' ')
+                     {
+                       output_paragraph[temp++] = '\n';
+
+                       /* We have correctly broken the line where we want
+                          to.  What we don't want is spaces following where
+                          we have decided to break the line.  We get rid of
+                          them. */
+                       {
+                         int t1 = temp;
+                         while (t1 < output_paragraph_offset
+                                && whitespace (output_paragraph[t1]))
+                           t1++;
+
+                         if (t1 != temp)
+                           {
+                             strncpy (&output_paragraph[temp],
+                                      &output_paragraph[t1],
+                                      (output_paragraph_offset - t1));
+                             output_paragraph_offset -= (t1 - temp);
+                           }
+                       }
+
+                       /* Filled, but now indent if that is right. */
+                       if (indented_fill && current_indent)
+                         {
+                           int buffer_len = ((output_paragraph_offset - temp)
+                                             + current_indent);
+                           char *temp_buffer = xmalloc (buffer_len);
+                           int indentation = 0;
+
+                           /* We have to shift any markers that are in
+                              front of the wrap point. */
+                           {
+                             register BRACE_ELEMENT *stack = brace_stack;
+
+                             while (stack)
+                               {
+                                 if (stack->pos > temp)
+                                   stack->pos += current_indent;
+                                 stack = stack->next;
+                               }
+                           }
+
+                           while (indentation != current_indent)
+                             temp_buffer[indentation++] = ' ';
+
+                           strncpy (&temp_buffer[current_indent],
+                                    &output_paragraph[temp],
+                                    buffer_len - current_indent);
+
+                           if (output_paragraph_offset + buffer_len
+                               >= paragraph_buffer_len)
+                             {
+                               char *tt =
+                               (char *) xrealloc (output_paragraph,
+                                     (paragraph_buffer_len += buffer_len));
+                               output_paragraph = tt;
+                             }
+                           strncpy (&output_paragraph[temp], temp_buffer, buffer_len);
+                           output_paragraph_offset += current_indent;
+                           free (temp_buffer);
+                         }
+                       output_column = 0;
+                       while (temp != output_paragraph_offset)
+                         output_column += get_char_len (output_paragraph[temp++]);
+                       output_column += len;
+                       break;
+                     }
+                 }
+             }
+         }
+       insert (character);
+       last_char_was_newline = false;
+       last_inserted_character = character;
+      }
+    }
+}
+
+/* Insert CHARACTER into OUTPUT_PARAGRAPH. */
+insert (character)
+     int character;
+{
+  output_paragraph[output_paragraph_offset++] = character;
+  if (output_paragraph_offset == paragraph_buffer_len)
+    {
+      output_paragraph =
+       (char *) xrealloc (output_paragraph,
+                     (paragraph_buffer_len += 100));
+    }
+}
+
+/* Remove upto COUNT characters of whitespace from the
+   the current output line.  If COUNT is less than zero,
+   then remove until none left. */
+kill_self_indent (count)
+     int count;
+{
+  /* Handle infinite case first. */
+  if (count < 0)
+    {
+      output_column = 0;
+      while (output_paragraph_offset)
+       {
+         if (whitespace (output_paragraph[output_paragraph_offset - 1]))
+           output_paragraph_offset--;
+         else
+           break;
+       }
+    }
+  else
+    {
+      while (output_paragraph_offset && count--)
+       if (whitespace (output_paragraph[output_paragraph_offset - 1]))
+         output_paragraph_offset--;
+       else
+         break;
+    }
+}
+
+flush_output ()
+{
+  register int i;
+
+  if (!output_paragraph_offset)
+    return;
+  for (i = 0; i < output_paragraph_offset; i++)
+    output_paragraph[i] &= 0x7f;
+
+  fwrite (output_paragraph, 1, output_paragraph_offset, output_stream);
+  output_position += output_paragraph_offset;
+  output_paragraph_offset = 0;
+}
+
+/* How to close a paragraph controlling the number of lines between
+   this one and the last one. */
+
+/* Paragraph spacing is controlled by this variable.  It is the number of
+   blank lines that you wish to appear between paragraphs.  A value of
+   1 creates a single blank line between paragraphs. */
+int paragraph_spacing = 1;
+
+
+/* Close the current paragraph, leaving no blank lines between them. */
+close_single_paragraph ()
+{
+  close_paragraph_with_lines (0);
+}
+
+close_paragraph_with_lines (lines)
+     int lines;
+{
+  int old_spacing = paragraph_spacing;
+  paragraph_spacing = lines;
+  close_paragraph ();
+  paragraph_spacing = old_spacing;
+}
+
+/* Non-zero means that start_paragraph () MUST be called before we pay
+   any attention to close_paragraph () calls. */
+int must_start_paragraph = 0;
+
+/* Close the currently open paragraph. */
+close_paragraph ()
+{
+  if (paragraph_is_open && !must_start_paragraph)
+    {
+      /* Gobble up blank lines that are extra... */
+      register int tindex = output_paragraph_offset;
+      register int c;
+      while (tindex &&
+            ((c = output_paragraph[tindex - 1]) == ' ' || c == '\n'))
+       output_paragraph[--tindex] = '\n';
+
+      output_paragraph_offset = tindex;
+
+      insert ('\n');
+      {
+       register int i;
+       for (i = 0; i < paragraph_spacing; i++)
+         insert ('\n');
+      }
+      flush_output ();
+      paragraph_is_open = false;
+      no_indent = false;
+    }
+  last_char_was_newline = true;
+}
+
+/* Begin a new paragraph. */
+start_paragraph ()
+{
+  close_paragraph ();          /* First close existing one. */
+
+  paragraph_is_open = true;
+
+  if (!must_start_paragraph)
+    {
+      output_column = 0;
+
+      /* If doing indentation, then insert the appropriate amount. */
+      if (!no_indent)
+       {
+         if (inhibit_paragraph_indentation || paragraph_start_indent < 0)
+           output_column = current_indent;
+         else
+           output_column = current_indent + paragraph_start_indent;
+
+         indent (output_column);
+       }
+    }
+  else
+    must_start_paragraph = 0;
+}
+
+/* Insert the indentation specified by AMOUNT. */
+indent (amount)
+     int amount;
+{
+  while (--amount >= 0)
+    insert (' ');
+}
+
+/* Search forward for STRING in input_text.
+   FROM says where where to start. */
+search_forward (string, from)
+     char *string;
+     int from;
+{
+  int len = strlen (string);
+
+  while (from < size_of_input_text)
+    {
+      if (strnicmp (input_text + from, string, len) == 0)
+       return (from);
+      from++;
+    }
+  return (-1);
+}
+
+/* Whoops, Unix doesn't have stricmp, or strnicmp. */
+
+/* Case independent string compare. */
+stricmp (string1, string2)
+     char *string1, *string2;
+{
+  char ch1, ch2;
+
+  for (;;)
+    {
+      ch1 = *string1++;
+      ch2 = *string2++;
+      if (!(ch1 | ch2))
+       return (0);
+
+      ch1 = coerce_to_upper (ch1);
+      ch2 = coerce_to_upper (ch2);
+
+      if (ch1 != ch2)
+       return (1);
+    }
+}
+
+/* Compare at most COUNT characters from string1 to string2.  Case
+   doesn't matter. */
+strnicmp (string1, string2, count)
+     char *string1, *string2;
+{
+  char ch1, ch2;
+
+  while (count)
+    {
+      ch1 = *string1++;
+      ch2 = *string2++;
+      if (coerce_to_upper (ch1) == coerce_to_upper (ch2))
+       count--;
+      else
+       break;
+    }
+  return (count);
+}
+
+enum insertion_type
+{
+  menu, quotation, lisp, example, smallexample, display,
+  itemize, format, enumerate, table, group, ifinfo,
+  defun, defvar, defopt, deffn, defspec, defmac,
+  bad_type
+};
+
+char *insertion_type_names[] = {
+         "menu", "quotation", "lisp", "example", "smallexample", "display",
+         "itemize", "format", "enumerate", "table", "group", "ifinfo",
+         "defun", "defvar", "defopt", "deffn", "defspec", "defmac",
+};
+
+int insertion_level = 0;
+typedef struct istack_elt
+{
+  struct istack_elt *next;
+  char *item_function;
+  int line_number;
+  int filling_enabled;
+  int indented_fill;
+  enum insertion_type insertion;
+  int inhibited;
+} INSERTION_ELT;
+
+INSERTION_ELT *insertion_stack = (INSERTION_ELT *) NULL;
+
+init_insertion_stack ()
+{
+  insertion_stack = (INSERTION_ELT *) NULL;
+}
+
+/* Return the type of the current insertion. */
+enum insertion_type
+current_insertion_type ()
+{
+  if (!insertion_level)
+    return (bad_type);
+  else
+    return (insertion_stack->insertion);
+}
+
+/* Return a pointer to the string which is the function
+   to wrap around items. */
+char *
+current_item_function ()
+{
+  if (!insertion_level)
+    return ((char *) NULL);
+  else
+    return (insertion_stack->item_function);
+}
+
+char *
+get_item_function ()
+{
+  char *item_function;
+  get_until ("\n", &item_function);
+  canon_white (item_function);
+  return (item_function);
+}
+
+ /* Push the state of the current insertion on the stack. */
+push_insertion (type, item_function)
+     enum insertion_type type;
+     char *item_function;
+{
+  INSERTION_ELT *new = (INSERTION_ELT *) xmalloc (sizeof (INSERTION_ELT));
+
+  new->item_function = item_function;
+  new->filling_enabled = filling_enabled;
+  new->indented_fill = indented_fill;
+  new->insertion = type;
+  new->line_number = line_number;
+  new->inhibited = inhibit_paragraph_indentation;
+  new->next = insertion_stack;
+  insertion_stack = new;
+  insertion_level++;
+}
+
+ /* Pop the value on top of the insertion stack into the
+    global variables. */
+pop_insertion ()
+{
+  INSERTION_ELT *temp = insertion_stack;
+  if (temp == (INSERTION_ELT *) NULL)
+    return;
+  inhibit_paragraph_indentation = temp->inhibited;
+  filling_enabled = insertion_stack->filling_enabled;
+  indented_fill = insertion_stack->indented_fill;
+  free_and_clear (&(temp->item_function));
+  insertion_stack = insertion_stack->next;
+  free (temp);
+  insertion_level--;
+}
+
+ /* Return a pointer to the print name of this
+    enumerated type. */
+char *
+insertion_type_pname (type)
+     enum insertion_type type;
+{
+  if ((int) type < (int) bad_type)
+    return (insertion_type_names[(int) type]);
+  else
+    return ("Broken-Type in insertion_type_pname");
+}
+
+/* Return the insertion_type associated with NAME.
+   If the type is not one of the known ones, return BAD_TYPE. */
+enum insertion_type
+find_type_from_name (name)
+     char *name;
+{
+  int index = 0;
+  while (index < (int) bad_type)
+    {
+      if (stricmp (name, insertion_type_names[index]) == 0)
+       return (enum insertion_type) index;
+      index++;
+    }
+  return (bad_type);
+}
+
+do_nothing ()
+{
+}
+
+int
+defun_insertion (type)
+     enum insertion_type type;
+{
+  return (type == defun ||
+         type == defvar ||
+         type == defopt ||
+         type == deffn ||
+         type == defspec ||
+         type == defmac);
+}
+
+/* Non-zero means that we are currently hacking the insides of an
+   insertion which would use a fixed width font. */
+int in_fixed_width_font = 0;
+
+/* This is where the work for all the "insertion" style
+   commands is done.  A huge switch statement handles the
+   various setups, and generic code is on both sides. */
+begin_insertion (type)
+     enum insertion_type type;
+{
+  int no_discard = 0;
+
+  close_paragraph ();
+
+  if (defun_insertion (type))
+    {
+      push_insertion (type, savestring (""));
+      no_discard = 1;
+    }
+  else
+    push_insertion (type, get_item_function ());
+
+  filling_enabled = false;     /* the general case for insertions. */
+  inhibit_paragraph_indentation = 1;
+  no_indent = false;
+
+  switch (type)
+    {
+    case menu:
+      add_word ("* Menu:\n");
+      in_menu++;
+      discard_until ("\n");
+      input_text_offset--;
+      /* discard_until () has already counted the newline.  Discount it. */
+      line_number--;
+      return;
+
+      /* I think @quotation is meant to do filling.
+        If you don't want filling, then use @example. */
+    case quotation:
+      last_char_was_newline = 0;
+      indented_fill = filling_enabled = true;
+      current_indent += default_indentation_increment;
+      break;
+
+      /* Just like @example, but no indentation. */
+    case format:
+      in_fixed_width_font++;
+      break;
+
+    case display:
+    case example:
+    case smallexample:
+    case lisp:
+      last_char_was_newline = 0;
+      current_indent += default_indentation_increment;
+      in_fixed_width_font++;
+      break;
+
+    case table:
+    case itemize:
+      current_indent += default_indentation_increment;
+      filling_enabled = indented_fill = true;
+
+      /* Make things work for losers who forget the itemize syntax. */
+      if (type == itemize)
+       {
+         if (!(*insertion_stack->item_function))
+           {
+             free (insertion_stack->item_function);
+             insertion_stack->item_function = savestring ("*");
+           }
+       }
+      break;
+
+    case enumerate:
+      inhibit_paragraph_indentation = 0;
+      current_indent += default_indentation_increment;
+      start_numbering (1);
+      filling_enabled = indented_fill = true;
+      break;
+
+    case group:
+      inhibit_paragraph_indentation = 0;
+      break;
+
+    case ifinfo:
+      /* Undo whatever we just did.  This is a no-op. */
+      inhibit_paragraph_indentation = 0;
+      filling_enabled = insertion_stack->filling_enabled;
+      indented_fill = insertion_stack->indented_fill;
+      break;
+
+    case defun:
+    case defvar:
+    case defopt:
+    case deffn:
+    case defspec:
+    case defmac:
+      filling_enabled = indented_fill = true;
+      current_indent += default_indentation_increment;
+      break;
+    }
+  if (!no_discard)
+    discard_until ("\n");
+}
+
+/* Try to end the quotation with the specified type.
+   Like begin_insertion (), this uses a giant switch statement as
+   well.  A big difference is that you *must* pass a valid type to
+   this function, and a value of bad_type gets translated to match
+   the value currently on top of the stack.  If, however, the value
+   passed is a valid type, and it doesn't match the top of the
+   stack, then we produce an error.  Should fix this, somewhat
+   unclean. */
+end_insertion (type)
+     enum insertion_type type;
+{
+  enum insertion_type temp_type;
+
+  if (!insertion_level)
+    return;
+
+  temp_type = current_insertion_type ();
+  if (type == bad_type)
+    type = temp_type;
+
+  if (type != temp_type)
+    {
+      line_error ("Expected `%s', but saw `%s'.  Token unread",
+            insertion_type_pname (temp_type), insertion_type_pname (type));
+      return;
+    }
+  pop_insertion ();
+
+  switch (type)
+    {
+
+    case menu:
+      in_menu--;               /* no longer hacking menus. */
+      break;
+
+    case enumerate:
+      stop_numbering ();
+      current_indent -= default_indentation_increment;
+      break;
+
+    case group:
+    case ifinfo:
+      break;
+
+    case format:
+    case example:
+    case smallexample:
+    case display:
+    case lisp:
+      in_fixed_width_font--;
+      current_indent -= default_indentation_increment;
+      break;
+
+    default:
+      current_indent -= default_indentation_increment;
+      break;
+    }
+  close_paragraph ();
+}
+
+/* Insertions cannot cross certain boundaries, such as node beginnings.  In
+   code that creates such boundaries, you should call discard_insertions ()
+   before doing anything else.  It prints the errors for you, and cleans up
+   the insertion stack. */
+discard_insertions ()
+{
+  int real_line_number = line_number;
+  while (insertion_stack)
+    {
+      if (insertion_stack->insertion == ifinfo)
+       break;
+      else
+       {
+         char *offender = (char *) insertion_type_pname (insertion_stack->insertion);
+
+         line_number = insertion_stack->line_number;
+         line_error ("This `%s' doesn't have a matching `%cend %s'", offender,
+                     COMMAND_PREFIX, offender);
+         pop_insertion ();
+       }
+    }
+  line_number = real_line_number;
+}
+
+/* MAX_NS is the maximum nesting level for enumerations.  I picked 100
+   which seemed reasonable.  This doesn't control the number of items,
+   just the number of nested lists. */
+#define max_ns 100
+int number_stack[max_ns];
+int number_offset = 0;
+int the_current_number = 0;
+
+start_numbering (at_number)
+{
+  if (number_offset + 1 == max_ns)
+    {
+      line_error ("Enumeration stack overflow");
+      return;
+    }
+  number_stack[number_offset++] = the_current_number;
+  the_current_number = at_number;
+}
+
+stop_numbering ()
+{
+  the_current_number = number_stack[--number_offset];
+  if (number_offset < 0)
+    number_offset = 0;
+}
+
+ /* Place a number into the output stream. */
+number_item ()
+{
+  char temp[10];
+  sprintf (temp, "%d. ", the_current_number);
+  indent (output_column += (current_indent - strlen (temp)));
+  add_word (temp);
+  the_current_number++;
+}
+
+/* The actual commands themselves. */
+
+/* Commands which insert themselves. */
+insert_self ()
+{
+  add_word (command);
+}
+
+/* Force line break */
+cm_asterisk ()
+{
+  /* Force a line break in the output. */
+  insert ('\n');
+  indent (output_column = current_indent);
+}
+
+/* Insert ellipsis. */
+cm_dots (arg)
+     int arg;
+{
+  if (arg == START)
+    add_word ("...");
+}
+
+cm_bullet (arg)
+     int arg;
+{
+  if (arg == START)
+    add_char ('*');
+}
+
+cm_minus (arg)
+     int arg;
+{
+  if (arg == START)
+    add_char ('-');
+}
+
+/* Insert "TeX". */
+cm_TeX (arg)
+     int arg;
+{
+  if (arg == START)
+    add_word ("TeX");
+}
+
+cm_copyright (arg)
+     int arg;
+{
+  if (arg == START)
+    add_word ("(C)");
+}
+
+cm_code (arg)
+     int arg;
+{
+  extern int printing_index;
+
+  if (printing_index)
+    return;
+
+  if (arg == START)
+    add_char ('`');
+  else
+    add_word ("'");
+}
+
+cm_samp (arg)
+     int arg;
+{
+  cm_code (arg);
+}
+
+cm_file (arg)
+     int arg;
+{
+  cm_code (arg);
+}
+
+cm_kbd (arg)
+     int arg;
+{
+  cm_code (arg);
+}
+
+cm_key (arg)
+     int arg;
+{
+}
+
+ /* Convert the character at position-1 into CTL. */
+cm_ctrl (arg, position)
+     int arg, position;
+{
+  if (arg == END)
+    output_paragraph[position - 1] = CTL (output_paragraph[position - 1]);
+}
+
+/* Small Caps in makeinfo just does all caps. */
+cm_sc (arg, start_pos, end_pos)
+     int arg, start_pos, end_pos;
+{
+  if (arg == END)
+    {
+      while (start_pos < end_pos)
+       {
+         output_paragraph[start_pos] =
+           coerce_to_upper (output_paragraph[start_pos]);
+         start_pos++;
+       }
+    }
+}
+
+/* @var in makeinfo just uppercases the text. */
+cm_var (arg, start_pos, end_pos)
+     int arg, start_pos, end_pos;
+{
+  if (arg == END)
+    {
+      while (start_pos < end_pos)
+       {
+         output_paragraph[start_pos] =
+           coerce_to_upper (output_paragraph[start_pos]);
+         start_pos++;
+       }
+    }
+}
+
+cm_dfn (arg, position)
+     int arg, position;
+{
+  add_char ('"');
+}
+
+cm_emph (arg)
+     int arg;
+{
+  add_char ('*');
+}
+
+cm_strong (arg, position)
+     int arg, position;
+{
+  cm_emph (arg);
+}
+
+cm_cite (arg, position)
+     int arg, position;
+{
+  if (arg == START)
+    add_word ("``");
+  else
+    add_word ("''");
+}
+
+cm_italic (arg)
+{
+}
+
+cm_bold (arg)
+{
+  cm_italic (arg);
+}
+
+cm_roman (arg)
+{
+}
+
+cm_title (arg)
+{
+  cm_italic (arg);
+}
+
+cm_refill ()
+{
+}
+
+/* Prevent the argument from being split across two lines. */
+cm_w (arg)
+     int arg;
+{
+  if (arg == START)
+    non_splitting_words++;
+  else
+    non_splitting_words--;
+}
+
+
+/* Explain that this command is obsolete, thus the user shouldn't
+   do anything with it. */
+cm_obsolete (arg)
+{
+  if (arg == START)
+    warning ("The command `@%s' is obsolete", command);
+}
+
+/* Insert the text following input_text_offset up to the end of the line
+   in a new, separate paragraph.  Directly underneath it, insert a
+   line of WITH_CHAR, the same length of the inserted text. */
+insert_and_underscore (with_char)
+     int with_char;
+{
+  int len, i, old_no_indent;
+  char *temp;
+
+  close_paragraph ();
+  filling_enabled =  indented_fill = false;
+  old_no_indent = no_indent;
+  no_indent = true;
+  get_rest_of_line (&temp);
+
+  len = output_position;
+  execute_string ("%s\n", temp);
+  free (temp);
+
+  len = ((output_position + output_paragraph_offset) - 1) - len;
+  for (i = 0; i < len; i++)
+    add_char (with_char);
+  insert ('\n');
+  close_paragraph ();
+  filling_enabled = true;
+  no_indent = old_no_indent;
+}
+
+cm_chapter ()
+{
+  insert_and_underscore ('*');
+}
+
+cm_section ()
+{
+  insert_and_underscore ('=');
+}
+
+cm_subsection ()
+{
+  insert_and_underscore ('-');
+}
+
+cm_subsubsection ()
+{
+  insert_and_underscore ('.');
+}
+
+cm_unnumbered ()
+{
+  cm_chapter ();
+}
+
+cm_unnumberedsec ()
+{
+  cm_section ();
+}
+
+cm_unnumberedsubsec ()
+{
+  cm_subsection ();
+}
+
+cm_unnumberedsubsubsec ()
+{
+  cm_subsubsection ();
+}
+
+cm_appendix ()
+{
+  cm_chapter ();
+}
+
+cm_appendixsec ()
+{
+  cm_section ();
+}
+
+cm_appendixsubsec ()
+{
+  cm_subsection ();
+}
+
+cm_appendixsubsubsec ()
+{
+  cm_subsubsection ();
+}
+
+cm_majorheading ()
+{
+  cm_chapheading ();
+}
+
+cm_chapheading ()
+{
+  cm_chapter ();
+}
+
+cm_heading ()
+{
+  cm_section ();
+}
+
+cm_subheading ()
+{
+  cm_subsection ();
+}
+
+cm_subsubheading ()
+{
+  cm_subsubsection ();
+}
+
+
+/* **************************************************************** */
+/*                                                                 */
+/*                Adding nodes, and making tags                    */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Start a new tag table. */
+init_tag_table ()
+{
+  while (tag_table != (TAG_ENTRY *) NULL)
+    {
+      TAG_ENTRY *temp = tag_table;
+      free (temp->node);
+      free (temp->prev);
+      free (temp->next);
+      free (temp->up);
+      tag_table = tag_table->next_ent;
+      free (temp);
+    }
+}
+
+write_tag_table ()
+{
+  return (write_tag_table_internal (false));   /* Not indirect. */
+}
+
+write_tag_table_indirect ()
+{
+  return (write_tag_table_internal (true));
+}
+
+/* Write out the contents of the existing tag table.
+   INDIRECT_P says how to format the output. */
+write_tag_table_internal (indirect_p)
+     boolean indirect_p;
+{
+  TAG_ENTRY *node = tag_table;
+
+  filling_enabled = false;
+  must_start_paragraph = 0;
+  close_paragraph ();
+  if (!indirect_p)
+    insert ('\n');
+  add_word_args ("\037\nTag Table:\n%s", indirect_p ? "(Indirect)\n" : "");
+
+  while (node != (TAG_ENTRY *) NULL)
+    {
+      add_word_args ("Node: %s\177%d\n", node->node, node->position);
+      node = node->next_ent;
+    }
+  add_word ("\037\nEnd Tag Table\n");
+  flush_output ();
+}
+
+char *
+get_node_token ()
+{
+  char *string;
+
+  get_until_in_line (",", &string);
+
+  if (curchar () == ',')
+    input_text_offset++;
+
+  canon_white (string);
+
+  /* Allow things like @@nodename. */
+  normalize_node_name (string);
+
+  return (string);
+}
+
+/* Given a node name in STRING, remove double @ signs, replacing them
+   with just one. */
+normalize_node_name (string)
+     char *string;
+{
+  register int i, l = strlen (string);
+
+  for (i = 0; i < l; i++)
+    {
+      if (string[i] == '@' && string[i + 1] == '@')
+       {
+         strncpy (string + i, string + i + 1, l - i);
+         l--;
+       }
+    }
+}
+
+/* Look up NAME in the tag table, and return the associated
+   tag_entry.  If the node is not in the table return NULL. */
+TAG_ENTRY *
+find_node (name)
+     char *name;
+{
+  TAG_ENTRY *tag = tag_table;
+
+  while (tag != (TAG_ENTRY *) NULL)
+    {
+      if (stricmp (tag->node, name) == 0)
+       return (tag);
+      tag = tag->next_ent;
+    }
+  return ((TAG_ENTRY *) NULL);
+}
+
+/* Remember NODE and associates. */
+remember_node (node, prev, next, up, position, line_no, no_warn)
+     char *node, *prev, *next, *up;
+     int position, line_no, no_warn;
+{
+  /* Check for existence of this tag already. */
+  if (validating)
+    {
+      register TAG_ENTRY *tag = find_node (node);
+      if (tag)
+       {
+         line_error ("Node `%s' multiply defined (%d is first definition)",
+                     node, tag->line_no);
+         return;
+       }
+    }
+
+  /* First, make this the current node. */
+  current_node = node;
+
+  /* Now add it to the list. */
+  {
+    TAG_ENTRY *new = (TAG_ENTRY *) xmalloc (sizeof (TAG_ENTRY));
+    new->node = node;
+    new->prev = prev;
+    new->next = next;
+    new->up = up;
+    new->position = position;
+    new->line_no = line_no;
+    new->filename = node_filename;
+    new->touched = 0;          /* not yet referenced. */
+    new->flags = 0;
+    if (no_warn)
+      new->flags |= NO_WARN;
+    new->next_ent = tag_table;
+    tag_table = new;
+  }
+}
+
+/* Here is a structure which associates sectioning commands with
+   an integer, hopefully to reflect the `depth' of the current
+   section. */
+struct {
+  char *name;
+  int level;
+} section_alist[] = {
+  { "chapter", 1 },
+  { "section", 2},
+  { "subsec", 3},
+  { "subsubsec", 4},
+  { "unnumbered", 1},
+  { "unnumberedsec", 2},
+  { "unnumberedsubsec", 3},
+  { "unnumberedsubsubsec", 4},
+  { "appendix", 1},
+  { "appendixsec", 2},
+  { "appendixsubsec", 3},
+  { "appendixsubsubsec", 4},
+  { (char *)NULL, 0 }
+};
+
+/* Return an integer which identifies the type section present in TEXT. */
+int
+what_section (text)
+     char *text;
+{
+  int i, j;
+  char *t;
+
+  for (j = 0; text[j] && whitespace (text[j]) || text[j] == '\n'; j++);
+  if (text[j] != '@')
+    return (-1);
+
+  text = text + j + 1;
+
+  /* Handle italicized sectioning commands. */
+  if (*text == 'i')
+    text++;
+
+  for (i = 0; t = section_alist[i].name; i++)
+    {
+      if (strncmp (t, text, strlen (t)) == 0)
+       return (section_alist[i].level);
+    }
+  return (-1);
+}
+
+/* The order is: nodename, nextnode, prevnode, upnode.
+   The next, prev, and up fields can be defaulted.
+   You must follow a node command which has those fields
+   defaulted with a sectioning command (e.g. @chapter) giving
+   the "level" of that node.  It is an error not to do so.
+   The defaults come from the menu in this nodes parent. */
+cm_node ()
+{
+  char *node, *prev, *next, *up;
+  int new_node_pos, defaulting, this_section, no_warn = 0;
+  extern int already_outputting_pending_notes;
+
+  if (strcmp (command, "nwnode") == 0)
+    no_warn = 1;
+
+  /* Get rid of unmatched brace arguments from previous commands. */
+  discard_braces ();
+
+  /* There also might be insertions left lying around that haven't been
+     ended yet.  Do that also. */
+  discard_insertions ();
+
+  if (!already_outputting_pending_notes)
+    {
+      close_paragraph ();
+      output_pending_notes ();
+      free_pending_notes ();
+    }
+
+  filling_enabled = indented_fill = false;
+  new_node_pos = output_position + 1;
+
+  node = get_node_token ();
+  next = get_node_token ();
+  prev = get_node_token ();
+  up = get_node_token ();
+
+  this_section = what_section (input_text + input_text_offset);
+
+  /* ??? The first \n in the following string shouldn't be there, but I have
+     to revamp the @example & @group things so that they always leave a \n
+     as the last character output.  Until that time, this is the only way
+     I can produce reliable output. */
+  no_indent = true;
+  add_word_args ("\n\037\nFile: %s,  Node: %s", pretty_output_filename, node);
+
+  /* Check for defaulting of this node's next, prev, and up fields. */
+  defaulting = ((strlen (next) == 0) &&
+               (strlen (prev) == 0) &&
+               (strlen (up) == 0));
+
+  /* If we are defaulting, then look at the immediately following
+     sectioning command (error if none) to determine the node's
+     level.  Find the node that contains the menu mentioning this node
+     that is one level up (error if not found).  That node is the "Up"
+     of this node.  Default the "Next" and "Prev" from the menu. */
+  if (defaulting)
+    {
+      NODE_REF *last_ref = (NODE_REF *)NULL;
+      NODE_REF *ref = node_references;
+
+      if (this_section < 0)
+       {
+         char *polite_section_name = "chapter";
+         int i;
+
+         for (i = 0; section_alist[i].name; i++)
+           if (section_alist[i].level == current_section + 1)
+             {
+               polite_section_name = section_alist[i].name;
+               break;
+             }
+
+         line_error
+           ("Node `%s' requires a sectioning command (e.g. @%s)",
+            node, polite_section_name);
+       }
+      else
+       {
+         while (ref)
+           {
+             if (ref->section == (this_section - 1) &&
+                 ref->type == menu_reference &&
+                 stricmp (ref->node, node) == 0)
+               {
+                 free (up);
+                 up = savestring (ref->containing_node);
+
+                 if (last_ref &&
+                     strcmp
+                     (last_ref->containing_node, ref->containing_node) == 0)
+                   {
+                     free (next);
+                     next = savestring (last_ref->node);
+                   }
+
+                 if (ref->next &&
+                     strcmp
+                     (ref->next->containing_node, ref->containing_node) == 0)
+                   {
+                     free (prev);
+                     prev = savestring (ref->next->node);
+                   }
+                 break;
+               }
+             last_ref = ref;
+             ref = ref->next;
+           }
+       }
+    }
+
+  if (*next)
+    add_word_args (",  Next: %s", next);
+  
+  if (*prev)
+    add_word_args (",  Prev: %s", prev);
+
+  if (*up)
+    add_word_args (",  Up: %s", up);
+
+  insert ('\n');
+  close_paragraph ();
+  no_indent = false;
+
+  if (!*node)
+    {
+      line_error ("No node name specified for `@%s' command", command);
+      free (node);
+      free (next);
+      free (prev);
+      free (up);
+    }
+  else
+    {
+      if (!*next) { free (next); next = (char *)NULL; }
+      if (!*prev) { free (prev); prev = (char *)NULL; }
+      if (!*up) { free (up); up = (char *)NULL; }
+      remember_node (node, prev, next, up, new_node_pos, line_number, no_warn);
+    }
+
+  /* Change the section only if there was a sectioning command. */
+  if (this_section >= 0)
+    current_section = this_section;
+
+  filling_enabled = true;
+}
+
+/* Validation of an info file.
+   Scan through the list of tag entrys touching the Prev, Next, and Up
+   elements of each.  It is an error not to be able to touch one of them,
+   except in the case of external node references, such as "(DIR)".
+
+   If the Prev is different from the Up,
+   then the Prev node must have a Next pointing at this node.
+
+   Every node except Top must have an Up.
+   The Up node must contain some sort of reference, other than a Next,
+   to this node.
+
+   If the Next is different from the Next of the Up,
+   then the Next node must have a Prev pointing at this node. */
+validate_file (filename, tag_table)
+     char *filename;
+     TAG_ENTRY *tag_table;
+{
+  char *old_input_filename = input_filename;
+  TAG_ENTRY *tags = tag_table;
+
+  while (tags != (TAG_ENTRY *) NULL)
+    {
+      register TAG_ENTRY *temp_tag;
+
+      input_filename = tags->filename;
+      line_number = tags->line_no;
+
+      /* If this node has a Next, then make sure that the Next exists. */
+      if (tags->next)
+       {
+         validate (tags->next, tags->line_no, "Next");
+
+         /* If the Next node exists, and there is no Up, then make
+            sure that the Prev of the Next points back. */
+         if (temp_tag = find_node (tags->next))
+           {
+             char *prev = temp_tag->prev;
+             if (!prev || (stricmp (prev, tags->node) != 0))
+               {
+                 line_error
+                   ("Node `%s''s Next field not pointed back to", tags->node);
+                 line_number = temp_tag->line_no;
+                 input_filename = temp_tag->filename;
+                 line_error
+                   ("This node (`%s') is the one with the bad `Prev'",
+                    temp_tag->node);
+                 input_filename = tags->filename;
+                 line_number = tags->line_no;
+                 temp_tag->flags |= PREV_ERROR;
+               }
+           }
+       }
+
+      /* Validate the Prev field if there is one, and we haven't already
+        complained about it in some way.  You don't have to have a Prev
+        field at this stage. */
+      if (!(tags->flags & PREV_ERROR) && tags->prev)
+       {
+         int valid = validate (tags->prev, tags->line_no, "Prev");
+
+         if (!valid)
+           tags->flags |= PREV_ERROR;
+         else
+           {
+             /* If the Prev field is not the same as the Up field,
+                then the node pointed to by the Prev field must have
+                a Next field which points to this node. */
+             if (tags->up && (stricmp (tags->prev, tags->up) != 0))
+               {
+                 temp_tag = find_node (tags->prev);
+                 if (!temp_tag->next ||
+                     (stricmp (temp_tag->next, tags->node) != 0))
+                   {
+                     line_error ("Node `%s''s Prev field not pointed back to",
+                                 tags->node);
+                     line_number = temp_tag->line_no;
+                     input_filename = temp_tag->filename;
+                     line_error
+                       ("This node (`%s') is the one with the bad `Next'",
+                        temp_tag->node);
+                     input_filename = tags->filename;
+                     line_number = tags->line_no;
+                     temp_tag->flags |= NEXT_ERROR;
+                   }
+               }
+           }
+       }
+
+      if (!tags->up && (stricmp (tags->node, "Top") != 0))
+       line_error ("Node `%s' is missing an \"Up\" field", tags->node);
+      else if (tags->up)
+       {
+         int valid = validate (tags->up, tags->line_no, "Up");
+
+         /* If node X has Up: Y, then warn if Y fails to have a menu item
+            or note pointing at X, if Y isn't of the form "(Y)". */
+         if (valid && *tags->up != '(')
+           {
+             NODE_REF *nref, *tref, *list;
+             NODE_REF *find_node_reference ();
+
+             tref = (NODE_REF *) NULL;
+             list = node_references;
+
+             for (;;)
+               {
+                 if (!(nref = find_node_reference (tags->node, list)))
+                   break;
+
+                 if (stricmp (nref->containing_node, tags->up) == 0)
+                   {
+                     if (nref->type != menu_reference)
+                       {
+                         tref = nref;
+                         list = nref->next;
+                       }
+                     else
+                       break;
+                   }
+                 list = nref->next;
+               }
+
+             if (!nref)
+               {
+                 temp_tag = find_node (tags->up);
+                 line_number = temp_tag->line_no;
+                 filename = temp_tag->filename;
+                 if (!tref)
+                   line_error ("`%s' has an Up field of `%s', but `%s' has no menu item for `%s'",
+                               tags->node, tags->up, tags->up, tags->node);
+                 line_number = tags->line_no;
+                 filename = tags->filename;
+               }
+           }
+       }
+      tags = tags->next_ent;
+    }
+
+  validate_other_references (node_references);
+  /* We have told the user about the references which didn't exist.
+     Now tell him about the nodes which aren't referenced. */
+
+  tags = tag_table;
+  while (tags != (TAG_ENTRY *) NULL)
+    {
+      /* Special hack.  If the node in question appears to have
+         been referenced more than REFERENCE_WARNING_LIMIT times,
+         give a warning. */
+      if (tags->touched > reference_warning_limit)
+       {
+         input_filename = tags->filename;
+         line_number = tags->line_no;
+         warning ("Node `%s' has been referenced %d times",
+                  tags->node, tags->touched);
+       }
+
+      if (tags->touched == 0)
+       {
+         input_filename = tags->filename;
+         line_number = tags->line_no;
+
+         /* Notice that the node "Top" is special, and doesn't have to
+            be referenced. */
+         if (stricmp (tags->node, "Top") != 0)
+           warning ("Unreferenced node `%s'", tags->node);
+       }
+      tags = tags->next_ent;
+    }
+  input_filename = old_input_filename;
+}
+
+/* Return 1 if tag correctly validated, or 0 if not. */
+validate (tag, line, label)
+     char *tag;
+     int line;
+     char *label;
+{
+  TAG_ENTRY *result;
+
+  /* If there isn't a tag to verify, or if the tag is in another file,
+     then it must be okay. */
+  if (!tag || !*tag || *tag == '(')
+    return (1);
+
+  /* Otherwise, the tag must exist. */
+  result = find_node (tag);
+
+  if (!result)
+    {
+      line_number = line;
+      line_error ("Validation error.  `%s' field points to node `%s', which doesn't exist",
+                 label, tag);
+      return (0);
+    }
+  result->touched++;
+  return (1);
+}
+
+/* Split large output files into a series of smaller files.  Each file
+   is pointed to in the tag table, which then gets written out as the
+   original file.  The new files have the same name as the original file
+   with a "-num" attached.  SIZE is the largest number of bytes to allow
+   in any single split file. */
+split_file (filename, size)
+     char *filename;
+     int size;
+{
+  char *root_filename, *root_pathname;
+  char *the_file, *filename_part ();
+  struct stat fileinfo;
+  char *the_header;
+  int header_size;
+
+  /* Can only do this to files with tag tables. */
+  if (!tag_table)
+    return;
+
+  if (size == 0)
+    size = DEFAULT_SPLIT_SIZE;
+
+  if ((stat (filename, &fileinfo) != 0) ||
+      (fileinfo.st_size < SPLIT_SIZE_THRESHOLD))
+    return;
+
+  the_file = find_and_load (filename);
+  if (!the_file)
+    return;
+
+  root_filename = filename_part (filename);
+  root_pathname = pathname_part (filename);
+
+  if (!root_pathname)
+    root_pathname = savestring ("");
+
+  /* Start splitting the file.  Walk along the tag table
+     outputting sections of the file.  When we have written
+     all of the nodes in the tag table, make the top-level
+     pointer file, which contains indirect pointers and
+     tags for the nodes. */
+  {
+    int which_file = 1;
+    TAG_ENTRY *tags = tag_table;
+    char *indirect_info = (char *)NULL;
+
+    /* Remember the `header' of this file.  The first tag in the file is
+       the bottom of the header; the top of the file is the start. */
+    the_header = xmalloc (1 + (header_size = (tags->position - 2)));
+    bcopy (the_file, the_header, header_size);
+
+    while (tags)
+      {
+       int file_top, file_bot, limit;
+
+       /* Have to include the Control-_. */
+       file_top = file_bot = tags->position - 2;
+       limit = file_top + size;
+
+       /* If the rest of this file is only one node, then
+          that is the entire subfile. */
+       if (!tags->next_ent)
+         {
+           int i = tags->position + 1;
+           char last_char = the_file[i];
+
+           while (i < fileinfo.st_size)
+             {
+               if ((the_file[i] == '\037') &&
+                   ((last_char == '\n') ||
+                    (last_char == '\014')))
+                 break;
+               else
+                 last_char = the_file[i];
+               i++;
+             }
+           file_bot = i;
+           tags = tags->next_ent;
+           goto write_region;
+         }
+
+       /* Otherwise, find the largest number of nodes that can fit in
+          this subfile. */
+       for (; tags; tags = tags->next_ent)
+         {
+           if (!tags->next_ent)
+             {
+               /* This entry is the last node.  Search forward for the end
+                  of this node, and that is the end of this file. */
+               int i = tags->position + 1;
+               char last_char = the_file[i];
+
+               while (i < fileinfo.st_size)
+                 {
+                   if ((the_file[i] == '\037') &&
+                       ((last_char == '\n') ||
+                        (last_char == '\014')))
+                     break;
+                   else
+                     last_char = the_file[i];
+                   i++;
+                 }
+               file_bot = i;
+
+               if (file_bot < limit)
+                 {
+                   tags = tags->next_ent;
+                   goto write_region;
+                 }
+               else
+                 {
+                   /* Here we want to write out everything before the last
+                      node, and then write the last node out in a file
+                      by itself. */
+                   file_bot = tags->position;
+                   goto write_region;
+                 }
+             }
+
+           if (tags->next_ent->position > limit)
+             {
+               if ((tags->position) - 2 == file_top)
+                 tags = tags->next_ent;
+               file_bot = tags->position;
+             write_region:
+               {
+                 int fd;
+                 char *split_file = xmalloc (10 + strlen (root_pathname)
+                                             + strlen (root_filename));
+                 sprintf (split_file,
+                      "%s%s-%d", root_pathname, root_filename, which_file);
+
+                 if (((fd = open (split_file, O_WRONLY | O_TRUNC | O_CREAT, 0666)) < 0)
+                     || (write (fd, the_header, header_size) != header_size)
+                     || (write (fd, the_file + file_top, file_bot - file_top)
+                         != (file_bot - file_top))
+                     || ((close (fd)) < 0))
+                   {
+                     perror (split_file);
+                     close (fd);
+                     exit (FATAL);
+                   }
+
+                 if (!indirect_info)
+                   {
+                     indirect_info = the_file + file_top;
+                     sprintf (indirect_info, "\037\nIndirect:\n");
+                     indirect_info += strlen (indirect_info);
+                   }
+
+                 sprintf (indirect_info, "%s-%d: %d\n",
+                          root_filename, which_file, file_top);
+
+                 free (split_file);
+                 indirect_info += strlen (indirect_info);
+                 which_file++;
+                 break;
+               }
+             }
+         }
+      }
+
+    /* We have sucessfully created the subfiles.  Now write out the
+       original again.  We must use `output_stream', or
+       write_tag_table_indirect () won't know where to place the output. */
+    output_stream = fopen (filename, "w");
+    if (!output_stream)
+      {
+       perror (filename);
+       exit (FATAL);
+      }
+
+    {
+      int distance = indirect_info - the_file;
+      fwrite (the_file, 1, distance, output_stream);
+
+      /* Inhibit newlines. */
+      paragraph_is_open = false;
+
+      write_tag_table_indirect ();
+      fclose (output_stream);
+      free (the_header);
+      free (the_file);
+      return;
+    }
+  }
+}
+
+/* Some menu hacking.  This is used to remember menu references while
+   reading the input file.  After the output file has been written, if
+   validation is on, then we use the contents of NODE_REFERENCES as a
+   list of nodes to validate. */
+char *
+reftype_type_string (type)
+     enum reftype type;
+{
+  switch (type)
+    {
+    case menu_reference:
+      return ("Menu");
+    case followed_reference:
+      return ("Followed-Reference");
+    default:
+      return ("Internal-bad-reference-type");
+    }
+}
+
+/* Remember this node name for later validation use. */
+remember_node_reference (node, line, type)
+     char *node;
+     int line;
+     enum reftype type;
+{
+  NODE_REF *temp = (NODE_REF *) xmalloc (sizeof (NODE_REF));
+
+  temp->next = node_references;
+  temp->node = savestring (node);
+  temp->line_no = line;
+  temp->section = current_section;
+  temp->type = type;
+  temp->containing_node = savestring (current_node);
+  temp->filename = node_filename;
+
+  node_references = temp;
+}
+
+validate_other_references (ref_list)
+     register NODE_REF *ref_list;
+{
+  char *old_input_filename = input_filename;
+
+  while (ref_list != (NODE_REF *) NULL)
+    {
+      input_filename = ref_list->filename;
+      validate (ref_list->node, ref_list->line_no,
+               reftype_type_string (ref_list->type));
+      ref_list = ref_list->next;
+    }
+  input_filename = old_input_filename;
+}
+
+/* Find NODE in REF_LIST. */
+NODE_REF *
+find_node_reference (node, ref_list)
+     char *node;
+     register NODE_REF *ref_list;
+{
+  while (ref_list)
+    {
+      if (stricmp (node, ref_list->node) == 0)
+       break;
+      ref_list = ref_list->next;
+    }
+  return (ref_list);
+}
+
+free_node_references ()
+{
+  register NODE_REF *list, *temp;
+
+  list = node_references;
+
+  while (list)
+    {
+      temp = list;
+      free (list->node);
+      free (list->containing_node);
+      list = list->next;
+      free (temp);
+    }
+  node_references = (NODE_REF *) NULL;
+}
+
+#define menu_starter "* "
+glean_node_from_menu ()
+{
+  /* This function gets called at the start of every line while inside of
+     a menu.  It checks to see if the line starts with "* ", and if so,
+     remembers the node reference that this menu refers to.
+
+     input_text_offset is at the \n just before the line start. */
+
+  int i, orig_offset = input_text_offset;
+  char *nodename;
+
+  if (strncmp (&input_text[input_text_offset + 1],
+              menu_starter,
+              strlen (menu_starter)) != 0)
+    return;
+  else
+    input_text_offset += strlen (menu_starter) + 1;
+
+  get_until_in_line (":", &nodename);
+  if (curchar () == ':')
+    input_text_offset++;
+  canon_white (nodename);
+
+  if (curchar () == ':')
+    goto save_node;
+  free (nodename);
+  get_rest_of_line (&nodename);
+
+  /* Special hack: If the nodename follows the menu item name,
+     then we have to read the rest of the line in order to find
+     out what the nodename is.  But we still have to read the
+     line later, in order to process any formatting commands that
+     might be present.  So un-count the carriage return that has just
+     been counted. */
+  line_number--;
+
+  canon_white (nodename);
+  for (i = 0; i < strlen (nodename); i++)
+    {
+      if (nodename[i] == '\t' ||
+         nodename[i] == '.' ||
+         nodename[i] == ',')
+       {
+         nodename[i] = '\0';
+         break;
+       }
+    }
+save_node:
+  normalize_node_name (nodename);
+  i = strlen (nodename);
+  if (i && nodename[i - 1] == ':')
+    nodename[i - 1] = '\0';
+
+  remember_node_reference (nodename, line_number, menu_reference);
+  free (nodename);
+  input_text_offset = orig_offset;
+}
+
+cm_menu ()
+{
+  begin_insertion (menu);
+}
+
+
+/* **************************************************************** */
+/*                                                                 */
+/*                     Cross Reference Hacking                     */
+/*                                                                 */
+/* **************************************************************** */
+
+char *
+get_xref_token ()
+{
+  char *string;
+
+  get_until_in_braces (",", &string);
+  if (curchar () == ',')
+    input_text_offset++;
+  fix_whitespace (string);
+  normalize_node_name (string);
+  return (string);
+}
+
+int px_ref_flag = 0;           /* Controls initial output string. */
+
+/* Make a cross reference. */
+cm_xref (arg)
+{
+  if (arg == START)
+    {
+      char *arg1, *arg2, *arg3, *arg4, *arg5;
+
+      arg1 = get_xref_token ();
+      arg2 = get_xref_token ();
+      arg3 = get_xref_token ();
+      arg4 = get_xref_token ();
+      arg5 = get_xref_token ();
+
+      add_word_args ("%s", px_ref_flag ? "*note " : "*Note ");
+
+      if (*arg5 || *arg4)
+       {
+         char *node_name;
+
+         if (!*arg2)
+           node_name = arg1;
+         else
+           node_name = arg2;
+
+         add_word_args ("%s: (%s)%s", arg2, arg4, arg1);
+         return;
+       }
+      else
+       remember_node_reference (arg1, line_number, followed_reference);
+
+      if (*arg3)
+       {
+         if (!*arg2)
+           {
+             add_word_args ("%s: %s", arg3, arg1);
+           }
+         else
+           {
+             add_word_args ("%s: %s", arg2, arg1);
+           }
+         return;
+       }
+
+      if (*arg2)
+       {
+         execute_string ("%s", arg2);
+         add_word_args (": %s", arg1);
+       }
+      else
+       {
+         add_word_args ("%s::", arg1);
+       }
+
+    }
+  else
+    {
+
+      /* Check to make sure that the next non-whitespace character is either
+         a period or a comma. input_text_offset is pointing at the "}" which
+         ended the xref or pxref command. */
+
+      int temp = input_text_offset + 1;
+
+      if (output_paragraph[output_paragraph_offset - 2] == ':' &&
+         output_paragraph[output_paragraph_offset - 1] == ':')
+       return;
+      while (temp < size_of_input_text)
+       {
+         if (cr_or_whitespace (input_text[temp]))
+           temp++;
+         else
+           {
+             if (input_text[temp] == '.' ||
+                 input_text[temp] == ',' ||
+                 input_text[temp] == '\t')
+               return;
+             else
+               {
+                 line_error ("Cross-reference must be terminated with a period or a comma");
+                 return;
+               }
+           }
+       }
+    }
+}
+
+cm_pxref (arg)
+     int arg;
+{
+  if (arg == START)
+    {
+      px_ref_flag++;
+      cm_xref (arg);
+      px_ref_flag--;
+    }
+  else
+    add_char ('.');
+}
+
+cm_inforef (arg)
+     int arg;
+{
+  if (arg == START)
+    {
+      char *node, *pname, *file;
+
+      node = get_xref_token ();
+      pname = get_xref_token ();
+      file = get_xref_token ();
+
+      add_word_args ("*note %s: (%s)%s", pname, file, node);
+    }
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*                     Insertion Command Stubs                     */
+/*                                                                 */
+/* **************************************************************** */
+
+cm_quotation ()
+{
+  begin_insertion (quotation);
+}
+
+cm_example ()
+{
+  begin_insertion (example);
+}
+
+cm_smallexample ()
+{
+  begin_insertion (smallexample);
+}
+
+cm_lisp ()
+{
+  begin_insertion (lisp);
+}
+
+cm_format ()
+{
+  begin_insertion (format);
+}
+
+cm_display ()
+{
+  begin_insertion (display);
+}
+
+cm_itemize ()
+{
+  begin_insertion (itemize);
+}
+
+cm_enumerate ()
+{
+  begin_insertion (enumerate);
+}
+
+cm_table ()
+{
+  begin_insertion (table);
+}
+
+cm_group ()
+{
+  begin_insertion (group);
+}
+
+cm_ifinfo ()
+{
+  begin_insertion (ifinfo);
+}
+
+cm_tex ()
+{
+  discard_until ("\n@end tex");
+  discard_until ("\n");
+}
+
+cm_iftex ()
+{
+  discard_until ("\n@end iftex");
+  discard_until ("\n");
+}
+
+cm_titlespec ()
+{
+  discard_until ("\n@end titlespec");
+  discard_until ("\n");
+}
+
+cm_titlepage ()
+{
+  discard_until ("\n@end titlepage");
+  discard_until ("\n");
+}
+
+cm_ignore ()
+{
+  discard_until ("\n@end ignore");
+  discard_until ("\n");
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*                     @itemx, @item                               */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Non-zero means a string is in execution, as opposed to a file. */
+int executing_string = 0;
+
+/* Execute the string produced by formatting the ARGs with FORMAT.  This
+   is like submitting a new file with @include. */
+execute_string (format, arg1, arg2, arg3, arg4, arg5)
+     char *format;
+{
+  static char temp_string[4000];
+  sprintf (temp_string, format, arg1, arg2, arg3, arg4, arg5);
+  strcat (temp_string, "@bye\n");
+  pushfile ();
+  input_text_offset = 0;
+  input_text = temp_string;
+  input_filename = savestring (input_filename);
+  size_of_input_text = strlen (temp_string);
+
+  executing_string++;
+  reader_loop ();
+
+  popfile ();
+  executing_string--;
+
+  free_and_clear (&command);
+  command = savestring ("not bye");
+}
+
+int itemx_flag = 0;
+
+cm_itemx ()
+{
+  itemx_flag++;
+  cm_item ();
+  itemx_flag--;
+}
+
+
+cm_item ()
+{
+  char *rest_of_line, *item_func;
+
+  /* Can only hack "@item" while inside of an insertion. */
+  if (insertion_level)
+    {
+      get_until ("\n", &rest_of_line);
+      canon_white (rest_of_line);
+      item_func = current_item_function ();
+
+      /* Okay, do the right thing depending on which insertion function
+        is active. */
+
+      switch (current_insertion_type ())
+       {
+       case menu:
+       case quotation:
+       case example:
+       case smallexample:
+       case lisp:
+       case format:
+       case display:
+       case group:
+       case ifinfo:
+         line_error ("The `@%s' command is meaningless within a `@%s' block",
+                     command,
+                     insertion_type_pname (current_insertion_type ()));
+         break;
+
+       case itemize:
+       case enumerate:
+         if (itemx_flag)
+           {
+             line_error ("@itemx is not meaningful inside of a `%s' block",
+                         insertion_type_pname (current_insertion_type ()));
+           }
+         else
+           {
+             start_paragraph ();
+             kill_self_indent (-1);
+             discard_until ("\n");
+             filling_enabled = indented_fill = true;
+
+             if (current_insertion_type () == itemize)
+               {
+                 indent (output_column = current_indent - 2);
+
+                 /* I need some way to determine whether this command
+                    takes braces or not.  I believe the user can type
+                    either "@bullet" or "@bullet{}".  Of course, they
+                    can also type "o" or "#" or whatever else they want. */
+                 if (item_func && *item_func)
+                   {
+                     if (*item_func == '@')
+                       if (item_func[strlen (item_func) - 1] != '}')
+                         execute_string ("%s{}", item_func);
+                       else
+                         execute_string ("%s", item_func);
+                     else
+                       execute_string ("%s", item_func);
+                   }
+                 insert (' ');
+                 output_column++;
+               }
+             else
+               number_item ();
+
+             /* Special hack.  This makes close paragraph ignore you until
+                the start_paragraph () function has been called. */
+             must_start_paragraph = 1;
+           }
+         break;
+
+       case table:
+         {
+           /* Get rid of extra characters. */
+           kill_self_indent (-1);
+
+           /* close_paragraph () almost does what we want.  The problem
+              is when paragraph_is_open, and last_char_was_newline, and
+              the last newline has been turned into a space, because
+              filling_enabled. I handle it here. */
+           if (last_char_was_newline && filling_enabled && paragraph_is_open)
+             insert ('\n');
+           close_paragraph ();
+
+           /* Indent on a new line, but back up one indentation level. */
+           /* force existing indentation. */
+           add_char ('i');
+           output_paragraph_offset--;
+           kill_self_indent (default_indentation_increment + 1);
+
+           /* Add item's argument to the line. */
+           filling_enabled = false;
+           if (!item_func && !(*item_func))
+             execute_string ("%s", rest_of_line);
+           else
+             execute_string ("%s{%s}", item_func, rest_of_line);
+
+           /* Start a new line, and let start_paragraph ()
+              do the indenting of it for you. */
+           close_single_paragraph ();
+           indented_fill = filling_enabled = true;
+         }
+       }
+      free (rest_of_line);
+    }
+  else
+    line_error ("@%s found outside of an insertion block", command);
+}
+
+\f
+/* **************************************************************** */
+/*                                                                 */
+/*                     Defun and Friends                           */
+/*                                                                 */
+/* **************************************************************** */
+
+/* The list of args that were passed to the def**** command. */
+char **defun_args = (char **)NULL;
+
+/* An alist mapping defun insertion types to the text that we use
+   to describe them. */
+struct {
+  enum insertion_type type;
+  char *title;
+} type_title_alist[] = {
+  { defun, "Function" },
+  { defmac, "Macro" },
+  { defspec, "Special form" },
+  { defopt, "Option" },
+  { deffn, (char *)NULL },
+  { defvar, "Variable" },
+  { (enum insertion_type)0, (char *)NULL }
+};
+
+/* Return the title string for this type of defun. */
+char *
+defun_title (type)
+     enum insertion_type type;
+{
+  register int i;
+
+  for (i = 0; type_title_alist[i].type || type_title_alist[i].title; i++)
+    if (type_title_alist[i].type == type)
+      return (type_title_alist[i].title);
+  return (char *)NULL;
+}
+
+/* Return a list of words from the contents of STRING.
+   You can group words with braces. */
+char **
+args_from_string (string)
+     char *string;
+{
+  char **result = (char **) NULL;
+  register int i, start, result_index, size;
+  int len, skip_til_brace = 0;
+
+  i = result_index = size = 0;
+
+  /* Get a token, and stuff it into RESULT.  The tokens are split
+     at spaces or tabs. */
+  while (string[i])
+    {
+      /* Skip leading whitespace. */
+      for (; string[i] && whitespace (string[i]); i++);
+
+      start = i;
+
+      if (!string[i])
+       return (result);
+
+      /* If this is a command which takes it's argument in braces, then
+        gobble the whole thing. */
+      if (string[i] == COMMAND_PREFIX)
+       {
+         register int j;
+         for (j = i; string[j] &&
+              !whitespace (string[j]) &&
+              string[j] != '{'; j++);
+
+         if (string[j] == '{')
+           {
+             while (string[j] && string[j] != '}')
+               j++;
+
+             if (string[j])
+               j++;
+
+             i = j;
+             goto add_arg;
+           }
+       }
+      
+      if (string[i] == '{' && !whitespace (string[i + 1]))
+       {
+         skip_til_brace = 1;
+         start = ++i;
+       }
+
+      /* Skip until whitespace or close brace. */
+      while (string[i] &&
+            ((skip_til_brace && string[i] != '}') ||
+             (!skip_til_brace && !whitespace (string[i]))))
+       i++;
+
+    add_arg:
+      len = i - start;
+      if (result_index + 2 >= size)
+       {
+         if (!size)
+           result = (char **) xmalloc ((size = 10) * (sizeof (char *)));
+         else
+           result =
+             (char **) xrealloc (result, ((size += 10) * (sizeof (char *))));
+       }
+      result[result_index] = (char *) xmalloc (1 + len);
+      strncpy (result[result_index], string + start, len);
+      result[result_index][len] = '\0';
+      result_index++;
+      result[result_index] = (char *) NULL;
+
+      if (skip_til_brace)
+       {
+         skip_til_brace = 0;
+         if (string[i])
+           i++;
+       }
+    }
+
+  return (result);
+}
+
+get_defun_args ()
+{
+  register int i;
+  char *line;
+
+  get_rest_of_line (&line);
+
+  if (defun_args)
+    {
+      for (i = 0; defun_args[i]; i++)
+       free (defun_args[i]);
+      free (defun_args);
+    }
+
+  defun_args = args_from_string (line);
+  free (line);
+}
+
+insert_defun_arg (string, where)
+     char *string;
+     int where;
+{
+  register int i;
+
+  for (i = 0; defun_args[i]; i++);
+  defun_args = (char **)xrealloc (defun_args, (i + 2) * sizeof (char *));
+  defun_args[i + 1] = (char *)NULL;
+
+  for (; i != where; --i)
+    defun_args[i] = defun_args[i - 1];
+
+  defun_args[i] = savestring (string);
+}
+
+/* Make the defun type insertion.
+   TYPE says which insertion this is.
+   TITLE is the string to describe the object being described, or NULL
+   for no title string.
+   X_P says not to start a new insertion if non-zero. */
+defun_internal (type, title, x_p)
+     enum insertion_type type;
+     char *title;
+     int x_p;
+{
+  register int i = 0;
+  char *type_name, *func_name;
+  int old_no_indent = no_indent;
+
+  get_defun_args ();
+
+  if (title)
+    insert_defun_arg (title, 0);
+
+  if (defun_args[0])
+    {
+      type_name = defun_args[0];
+      i++;
+
+      if (defun_args[1])
+       {
+         func_name = defun_args[1];
+         i++;
+       }
+      else
+       func_name = "";
+    }
+  else
+    type_name = "";
+
+  no_indent = true;
+  start_paragraph ();
+  execute_string (" * %s: %s", type_name, func_name);
+  no_indent = old_no_indent;
+
+  for (; defun_args[i]; i++)
+    {
+      if (*defun_args[i] == '&')
+       add_word_args (" %s", defun_args[i]);
+      else
+       execute_string (" @var{%s}", defun_args[i]);
+    }
+  add_char ('\n');
+  if (type == defvar || type == defopt)
+    execute_string ("@vindex %s\n", func_name);
+  else
+    execute_string ("@findex %s\n", func_name);
+
+  if (!x_p)
+    begin_insertion (type);
+  else
+    start_paragraph ();
+}
+
+/* Add an entry for a function, macro, special form, variable, or option.
+   If the name of the calling command ends in `x', then this is an extra
+   entry included in the body of an insertion of the same type. */
+cm_defun ()
+{
+  int x_p;
+  enum insertion_type type;
+  char *title, *temp = savestring (command);
+
+  x_p = (command[strlen (command) - 1] == 'x');
+
+  if (x_p)
+    temp[strlen (temp) - 1] = '\0';
+
+  type = find_type_from_name (temp);
+  free (temp);
+
+  /* If we are adding to an already existing insertion, then make sure
+     that we are already in an insertion of type TYPE. */
+  if (x_p &&
+      (!insertion_level || insertion_stack->insertion != type))
+    {
+      line_error ("Must be in a `%s' insertion in order to use `%s'x",
+                 command, command);
+      return;
+    }
+
+  title = defun_title (type);
+  defun_internal (type, title, x_p);
+}
+\f
+/* End existing insertion block. */
+cm_end ()
+{
+  char *temp;
+  enum insertion_type type;
+
+  if (!insertion_level)
+    {
+      line_error ("Unmatched `@%s'", command);
+      return;
+    }
+  get_rest_of_line (&temp);
+  canon_white (temp);
+
+  if (strlen (temp) == 0)
+    line_error ("`@%s' needs something after it", command);
+  type = find_type_from_name (temp);
+  if (type == bad_type)
+    {
+      line_error ("Bad argument to `%s', `%s', using `%s'",
+          command, temp, insertion_type_pname (current_insertion_type ()));
+    }
+  end_insertion (type);
+  free (temp);
+}
+
+\f
+/* **************************************************************** */
+/*                                                                 */
+/*                     Other Random Commands                       */
+/*                                                                 */
+/* **************************************************************** */
+
+/* noindent () used to do something valueable, but it didn't follow the
+   spec for noindent in the texinfo manual.  Now it does nothing, which,
+   in the case of makeinfo, is correct. */
+cm_noindent ()
+{
+/*  no_indent = true;
+  indented_fill = false; */
+}
+
+/* I don't know exactly what to do with this.  Should I allow
+   someone to switch filenames in the middle of output?  Since the
+   file could be partially written, this doesn't seem to make sense.
+   Another option: ignore it, since they don't *really* want to
+   switch files.  Finally, complain, or at least warn. */
+cm_setfilename ()
+{
+  char *filename;
+  get_rest_of_line (&filename);
+  /* warning ("`@%s %s' encountered and ignored", command, filename); */
+  free (filename);
+}
+
+cm_comment ()
+{
+  discard_until ("\n");
+}
+
+cm_br ()
+{
+  close_paragraph ();
+}
+
+ /* Insert the number of blank lines passed as argument. */
+cm_sp ()
+{
+  int lines;
+  char *line;
+
+/*  close_paragraph (); */
+  get_rest_of_line (&line);
+
+  sscanf (line, "%d", &lines);
+  while (lines--)
+    add_char ('\n');
+  free (line);
+}
+
+cm_settitle ()
+{
+  discard_until ("\n");
+}
+
+cm_need ()
+{
+}
+
+/* Start a new line with just this text on it.
+   Then center the line of text.
+   This always ends the current paragraph. */
+cm_center ()
+{
+  char *line;
+
+  close_paragraph ();
+  filling_enabled = indented_fill = false;
+
+  get_rest_of_line (&line);
+
+  if (strlen (line) < fill_column)
+    {
+      int i = (fill_column - strlen (line)) / 2;
+      while (i--)
+       insert (' ');
+    }
+  execute_string (line);
+  free (line);
+  insert ('\n');
+  close_paragraph ();
+  filling_enabled = true;
+}
+
+/* Show what an expression returns. */
+cm_result (arg)
+     int arg;
+{
+  if (arg == END)
+    add_word ("=>");
+}
+
+/* What an expression expands to. */
+cm_expansion (arg)
+     int arg;
+{
+  if (arg == END)
+    add_word ("==>");
+}
+
+/* Indicates two expressions are equivalent. */
+cm_equiv (arg)
+     int arg;
+{
+  if (arg == END)
+    add_word ("==");
+}
+
+/* What an expression may print. */
+cm_print (arg)
+     int arg;
+{
+  if (arg == END)
+    add_word ("-|");
+}
+
+/* An error signaled. */
+cm_error (arg)
+     int arg;
+{
+  if (arg == END)
+    add_word ("error-->");
+}
+
+/* The location of point in an example of a buffer. */
+cm_point (arg)
+     int arg;
+{
+  if (arg == END)
+    add_word ("-!-");
+}
+
+/* Start a new line with just this text on it.
+   The text is outdented one level if possible. */
+cm_exdent ()
+{
+  char *line;
+  int i = current_indent;
+
+  if (current_indent)
+    current_indent -= default_indentation_increment;
+
+  get_rest_of_line (&line);
+  close_single_paragraph ();
+  add_word_args ("%s", line);
+  current_indent = i;
+  free (line);
+  close_single_paragraph ();
+}
+
+cm_include ()
+{
+  cm_infoinclude ();
+}
+
+/* Remember this file, and move onto the next. */
+cm_infoinclude ()
+{
+  char *filename;
+
+  close_paragraph ();
+  get_rest_of_line (&filename);
+  pushfile ();
+
+  /* In verbose mode we print info about including another file. */
+  if (verbose_mode)
+    {
+      register int i = 0;
+      register FSTACK *stack = filestack;
+
+      for (i = 0, stack = filestack; stack; stack = stack->next, i++);
+
+      i *= 2;
+
+      printf ("%*s", i, "");
+      printf ("%c%s %s\n", COMMAND_PREFIX, command, filename);
+      fflush (stdout);
+    }
+
+  if (!find_and_load (filename))
+    {
+      extern char *sys_errlist[];
+      extern int errno, sys_nerr;
+      popfile ();
+
+      /* Cannot "@include foo", in line 5 of "/wh/bar". */
+      line_error ("`%c%s %s': %s", COMMAND_PREFIX, command, filename,
+                 ((errno < sys_nerr) ?
+                  sys_errlist[errno] : "Unknown file system error"));
+    }
+  free (filename);
+}
+
+/* The other side of a malformed expression. */
+misplaced_brace ()
+{
+  line_error ("Misplaced `}'");
+}
+
+/* Don't let the filling algorithm insert extra whitespace here. */
+cm_force_abbreviated_whitespace ()
+{
+}
+
+/* Make the output paragraph end the sentence here, even though it
+   looks like it shouldn't.  This also inserts the character which
+   invoked it. */
+cm_force_sentence_end ()
+{
+  add_char (META ((*command)));
+}
+
+/* Signals end of processing.  Easy to make this happen. */
+cm_bye ()
+{
+  input_text_offset = size_of_input_text;
+}
+
+cm_asis ()
+{
+}
+
+cm_setchapternewpage ()
+{
+  discard_until ("\n");
+}
+
+cm_smallbook ()
+{
+  discard_until ("\n");
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*                     Indexing Stuff                              */
+/*                                                                 */
+/* **************************************************************** */
+
+
+/* An index element... */
+typedef struct index_elt
+{
+  struct index_elt *next;
+  char *entry;                 /* The index entry itself. */
+  char *node;                  /* The node from whence it came. */
+  int code;                    /* Non-zero means add `@code{...}' when
+                                  printing this element. */
+} INDEX_ELT;
+
+/* A list of short-names for each index, and the index to that index in our
+   index array, the_indices.  In addition, for each index, it is remembered
+   whether that index is a code index or not.  Code indices have @code{}
+   inserted around the first word when they are printed with printindex. */
+typedef struct
+{
+  char *name;
+  int index;
+  int code;
+} INDEX_ALIST;
+
+INDEX_ALIST **name_index_alist = (INDEX_ALIST **) NULL;
+
+/* An array of pointers.  Each one is for a different index.  The
+   "synindex" command changes which array slot is pointed to by a
+   given "index". */
+INDEX_ELT **the_indices = (INDEX_ELT **) NULL;
+
+/* The number of defined indices. */
+int defined_indices = 0;
+
+/* We predefine these. */
+#define program_index 0
+#define function_index 1
+#define concept_index 2
+#define variable_index 3
+#define datatype_index 4
+#define key_index 5
+
+init_indices ()
+{
+  int i;
+
+  /* Create the default data structures. */
+
+  /* Initialize data space. */
+  if (!the_indices)
+    {
+      the_indices = (INDEX_ELT **) xmalloc ((1 + defined_indices) *
+                                           sizeof (INDEX_ELT *));
+      the_indices[defined_indices] = (INDEX_ELT *) NULL;
+
+      name_index_alist = (INDEX_ALIST **) xmalloc ((1 + defined_indices) *
+                                                  sizeof (INDEX_ALIST *));
+      name_index_alist[defined_indices] = (INDEX_ALIST *) NULL;
+    }
+
+  /* If there were existing indices, get rid of them now. */
+  for (i = 0; i < defined_indices; i++)
+    undefindex (name_index_alist[i]->name);
+
+  /* Add the default indices. */
+  defindex ("pg", 0);
+  defindex ("fn", 1);          /* "fn" is a code index.  */
+  defindex ("cp", 0);
+  defindex ("vr", 0);
+  defindex ("tp", 0);
+  defindex ("ky", 0);
+
+}
+
+/* Find which element in the known list of indices has this name.
+   Returns -1 if NAME isn't found. */
+int
+find_index_offset (name)
+     char *name;
+{
+  register int i;
+  for (i = 0; i < defined_indices; i++)
+    if (name_index_alist[i] &&
+       stricmp (name, name_index_alist[i]->name) == 0)
+      return (name_index_alist[i]->index);
+  return (-1);
+}
+
+/* Return a pointer to the entry of (name . index) for this name.
+   Return NULL if the index doesn't exist. */
+INDEX_ALIST *
+find_index (name)
+     char *name;
+{
+  int offset = find_index_offset (name);
+  if (offset > -1)
+    return (name_index_alist[offset]);
+  else
+    return ((INDEX_ALIST *) NULL);
+}
+
+/* Given an index name, return the offset in the_indices of this index,
+   or -1 if there is no such index. */
+translate_index (name)
+     char *name;
+{
+  INDEX_ALIST *which = find_index (name);
+
+  if (which)
+    return (which->index);
+  else
+    return (-1);
+}
+
+/* Return the index list which belongs to NAME. */
+INDEX_ELT *
+index_list (name)
+     char *name;
+{
+  int which = translate_index (name);
+  if (which < 0)
+    return ((INDEX_ELT *) - 1);
+  else
+    return (the_indices[which]);
+}
+
+/* Please release me, let me go... */
+free_index (index)
+     INDEX_ELT *index;
+{
+  INDEX_ELT *temp;
+
+  while ((temp = index) != (INDEX_ELT *) NULL)
+    {
+      free (temp->entry);
+      free (temp->node);
+      index = index->next;
+      free (temp);
+    }
+}
+
+/* Flush an index by name. */
+undefindex (name)
+     char *name;
+{
+  int i;
+  int which = find_index_offset (name);
+
+  if (which < 0)
+    return (which);
+
+  i = name_index_alist[which]->index;
+
+
+  free_index (the_indices[i]);
+  the_indices[i] = (INDEX_ELT *) NULL;
+
+  free (name_index_alist[which]->name);
+  free (name_index_alist[which]);
+  name_index_alist[which] = (INDEX_ALIST *) NULL;
+}
+
+/* Define an index known as NAME.  We assign the slot number.
+   CODE if non-zero says to make this a code index. */
+defindex (name, code)
+     char *name;
+     int code;
+{
+  register int i, slot;
+
+  /* If it already exists, flush it. */
+  undefindex (name);
+
+  /* Try to find an empty slot. */
+  slot = -1;
+  for (i = 0; i < defined_indices; i++)
+    if (!name_index_alist[i])
+      {
+       slot = i;
+       break;
+      }
+
+  if (slot < 0)
+    {
+      /* No such luck.  Make space for another index. */
+      slot = defined_indices;
+      defined_indices++;
+
+      name_index_alist = (INDEX_ALIST **) xrealloc (name_index_alist,
+                                                   (1 + defined_indices)
+                                                 * sizeof (INDEX_ALIST *));
+      the_indices = (INDEX_ELT **) xrealloc (the_indices,
+                                            (1 + defined_indices)
+                                            * sizeof (INDEX_ELT *));
+    }
+
+  /* We have a slot.  Start assigning. */
+  name_index_alist[slot] = (INDEX_ALIST *) xmalloc (sizeof (INDEX_ALIST));
+  name_index_alist[slot]->name = savestring (name);
+  name_index_alist[slot]->index = slot;
+  name_index_alist[slot]->code = code;
+
+  the_indices[slot] = (INDEX_ELT *) NULL;
+}
+
+/* Add the arguments to the current index command to the index NAME. */
+index_add_arg (name)
+     char *name;
+{
+  int which;
+  char *index_entry;
+  INDEX_ALIST *tem;
+
+  tem = find_index (name);
+
+  which = tem ? tem->index : -1;
+
+  /* close_paragraph (); */
+  get_rest_of_line (&index_entry);
+
+  if (which < 0)
+    {
+      line_error ("Unknown index reference `%s'", name);
+      free (index_entry);
+    }
+  else
+    {
+      INDEX_ELT *new = (INDEX_ELT *) xmalloc (sizeof (INDEX_ELT));
+      new->next = the_indices[which];
+      new->entry = index_entry;
+      new->node = current_node;
+      new->code = tem->code;
+      the_indices[which] = new;
+    }
+}
+
+#define INDEX_COMMAND_SUFFIX "index"
+
+/* The function which user defined index commands call. */
+gen_index ()
+{
+  char *name = savestring (command);
+  if (strlen (name) >= strlen ("index"))
+    name[strlen (name) - strlen ("index")] = '\0';
+  index_add_arg (name);
+  free (name);
+}
+
+/* Define a new index command.  Arg is name of index. */
+cm_defindex ()
+{
+  gen_defindex (0);
+}
+
+cm_defcodeindex ()
+{
+  gen_defindex (1);
+}
+
+gen_defindex (code)
+     int code;
+{
+  char *name;
+  get_rest_of_line (&name);
+
+  if (find_index (name))
+    {
+      line_error ("Index `%s' already exists", name);
+      free (name);
+      return;
+    }
+  else
+    {
+      char *temp = (char *) alloca (1 + strlen (name) + strlen ("index"));
+      sprintf (temp, "%sindex", name);
+      define_user_command (temp, gen_index, 0);
+      defindex (name, code);
+      free (name);
+    }
+}
+
+/* Append LIST2 to LIST1.  Return the head of the list. */
+INDEX_ELT *
+index_append (head, tail)
+     INDEX_ELT *head, *tail;
+{
+  register INDEX_ELT *t_head = head;
+
+  if (!t_head)
+    return (tail);
+
+  while (t_head->next)
+    t_head = t_head->next;
+  t_head->next = tail;
+  return (head);
+}
+
+/* Expects 2 args, on the same line.  Both are index abbreviations.
+   Make the first one be a synonym for the second one, i.e. make the
+   first one have the same index as the second one. */
+cm_synindex ()
+{
+  int redirector, redirectee;
+  char *temp;
+
+  skip_whitespace ();
+  get_until_in_line (" ", &temp);
+  redirectee = find_index_offset (temp);
+  skip_whitespace ();
+  free_and_clear (&temp);
+  get_until_in_line (" ", &temp);
+  redirector = find_index_offset (temp);
+  free (temp);
+  if (redirector < 0 || redirectee < 0)
+    {
+      line_error ("Unknown index reference");
+    }
+  else
+    {
+      /* I think that we should let the user make indices synonymous to
+         each other without any lossage of info.  This means that one can
+         say @synindex cp dt anywhere in the file, and things that used to
+         be in cp will go into dt. */
+      INDEX_ELT *i1 = the_indices[redirectee], *i2 = the_indices[redirector];
+
+      if (i1 || i2)
+       {
+         if (i1)
+           the_indices[redirectee] = index_append (i1, i2);
+         else
+           the_indices[redirectee] = index_append (i2, i1);
+       }
+
+      name_index_alist[redirectee]->index =
+       name_index_alist[redirector]->index;
+    }
+}
+
+cm_pindex ()                   /* Pinhead index. */
+{
+  index_add_arg ("pg");
+}
+
+cm_vindex ()                   /* variable index */
+{
+  index_add_arg ("vr");
+}
+
+cm_kindex ()                   /* key index */
+{
+  index_add_arg ("ky");
+}
+
+cm_cindex ()                   /* concept index */
+{
+  index_add_arg ("cp");
+}
+
+cm_findex ()                   /* function index */
+{
+  index_add_arg ("fn");
+}
+
+cm_tindex ()                   /* data type index */
+{
+  index_add_arg ("tp");
+}
+
+/* Sorting the index. */
+index_element_compare (element1, element2)
+     INDEX_ELT **element1, **element2;
+{
+  /* This needs to ignore leading non-text characters. */
+  return (strcmp ((*element1)->entry, (*element2)->entry));
+}
+
+/* Sort the index passed in INDEX, returning an array of
+   pointers to elements.  The array is terminated with a NULL
+   pointer.  We call qsort because it's supposed to be fast.
+   I think this looks bad. */
+INDEX_ELT **
+sort_index (index)
+     INDEX_ELT *index;
+{
+  INDEX_ELT *temp = index;
+  INDEX_ELT **array;
+  int count = 0;
+
+  while (temp != (INDEX_ELT *) NULL)
+    {
+      count++;
+      temp = temp->next;
+    }
+
+  /* We have the length.  Make an array. */
+
+  array = (INDEX_ELT **) xmalloc ((count + 1) * sizeof (INDEX_ELT *));
+  count = 0;
+  temp = index;
+
+  while (temp != (INDEX_ELT *) NULL)
+    {
+      array[count++] = temp;
+      temp = temp->next;
+    }
+  array[count] = (INDEX_ELT *) NULL;   /* terminate the array. */
+
+  /* Sort the array. */
+  qsort (array, count, sizeof (INDEX_ELT *), index_element_compare);
+
+  return (array);
+}
+
+/* Non-zero means that we are in the middle of printing an index. */
+int printing_index = 0;
+
+/* Takes one arg, a short name of an index to print.
+   Outputs a menu of the sorted elements of the index. */
+cm_printindex ()
+{
+  int item;
+  INDEX_ELT *index;
+  INDEX_ELT **array;
+  char *index_name;
+  int old_inhibitions = inhibit_paragraph_indentation;
+  boolean previous_filling_enabled_value = filling_enabled;
+
+  close_paragraph ();
+  get_rest_of_line (&index_name);
+
+  index = index_list (index_name);
+  if ((int) index < 0)
+    {
+      line_error ("Unknown index name `%s'", index_name);
+      free (index_name);
+      return;
+    }
+  else
+    free (index_name);
+
+  array = sort_index (index);
+
+  filling_enabled = false;
+  inhibit_paragraph_indentation = 1;
+  close_paragraph ();
+  add_word ("* Menu:\n\n");
+
+  printing_index = 1;
+  for (item = 0; (index = array[item]); item++)
+    {
+      /* If this particular entry should be printed as a "code" index,
+        then wrap the entry with "@code{...}". */
+      if (index->code)
+       execute_string ("* @code{%s}: %s.\n", index->entry, index->node);
+      else
+       execute_string ("* %s: %s.\n", index->entry, index->node);
+      flush_output ();
+    }
+  printing_index = 0;
+  free (array);
+  close_paragraph ();
+  filling_enabled = previous_filling_enabled_value;
+  inhibit_paragraph_indentation = old_inhibitions;
+}
+
+\f
+/* **************************************************************** */
+/*                                                                 */
+/*                 Making User Defined Commands                    */
+/*                                                                 */
+/* **************************************************************** */
+
+define_user_command (name, proc, needs_braces_p)
+     char *name;
+     FUNCTION *proc;
+     int needs_braces_p;
+{
+  int slot = user_command_array_len;
+  user_command_array_len++;
+
+  if (!user_command_array)
+    user_command_array = (COMMAND **) xmalloc (1 * sizeof (COMMAND *));
+
+  user_command_array = (COMMAND **) xrealloc (user_command_array,
+                                             (1 + user_command_array_len) *
+                                             sizeof (COMMAND *));
+
+  user_command_array[slot] = (COMMAND *) xmalloc (sizeof (COMMAND));
+  user_command_array[slot]->name = savestring (name);
+  user_command_array[slot]->proc = proc;
+  user_command_array[slot]->argument_in_braces = needs_braces_p;
+}
+
+/* Make ALIAS run the named FUNCTION.  Copies properties from FUNCTION. */
+define_alias (alias, function)
+     char *alias, *function;
+{
+}
+
+/* Some support for footnotes. */
+
+/* Footnotes are a new construct in Info.  We don't know the best method
+   of implementing them for sure, so we present two possiblities.
+
+MN   1) Make them look like followed references, with the reference
+        destinations in a makeinfo manufactured node or,
+
+BN   2) Make them appear at the bottom of the node that they originally
+        appeared in.
+*/
+
+#define MN 0
+#define BN 1
+
+int footnote_style = MN;
+boolean first_footnote_this_node = true;
+int footnote_count = 0;
+
+/* Set the footnote style based on he style identifier in STRING. */
+set_footnote_style (string)
+     char *string;
+{
+  if (stricmp (string, "MN") == 0)
+    {
+      footnote_style = MN;
+      return;
+    }
+
+  if (stricmp (string, "BN") == 0)
+    {
+      footnote_style = BN;
+      return;
+    }
+}
+
+typedef struct fn
+{
+  struct fn *next;
+  char *marker;
+  char *note;
+}  FN;
+
+FN *pending_notes = (FN *) NULL;
+
+/* A method for remembering footnotes.  Note that this list gets output
+   at the end of the current node. */
+remember_note (marker, note)
+     char *marker, *note;
+{
+  FN *temp = (FN *) xmalloc (sizeof (FN));
+
+  temp->marker = savestring (marker);
+  temp->note = savestring (note);
+  temp->next = pending_notes;
+  pending_notes = temp;
+  footnote_count++;
+}
+
+/* How to get rid of existing footnotes. */
+free_pending_notes ()
+{
+  FN *temp;
+
+  while ((temp = pending_notes) != (FN *) NULL)
+    {
+      free (temp->marker);
+      free (temp->note);
+      pending_notes = pending_notes->next;
+      free (temp);
+    }
+  first_footnote_this_node = true;
+  footnote_count = 0;
+}
+
+/* What to do when you see a @footnote construct. */
+
+ /* Handle a "footnote".
+    footnote *{this is a footnote}
+    where "*" is the marker character for this note. */
+cm_footnote ()
+{
+  char *marker;
+  char *note;
+
+  get_until ("{", &marker);
+  canon_white (marker);
+
+  /* Read the argument in braces. */
+  if (curchar () != '{')
+    {
+      line_error ("`@%s' expected more than just `%s'.  It needs something in `{...}'", command, marker);
+      free (marker);
+      return;
+    }
+  else
+    {
+      int braces = 1;
+      int temp = ++input_text_offset;
+      int len;
+
+      while (braces)
+       {
+         if (temp == size_of_input_text)
+           {
+             line_error ("No closing brace for footnote `%s'", marker);
+             return;
+           }
+         if (input_text[temp] == '{')
+           braces++;
+         else if (input_text[temp] == '}')
+           braces--;
+         temp++;
+       }
+
+      len = (temp - input_text_offset) - 1;
+      note = xmalloc (len + 1);
+      strncpy (note, &input_text[input_text_offset], len);
+      note[len] = '\0';
+      input_text_offset = temp;
+    }
+
+  if (!current_node || !*current_node)
+    {
+      line_error ("Footnote defined without parent node");
+      free (marker);
+      free (note);
+      return;
+    }
+
+  remember_note (marker, note);
+
+  switch (footnote_style)
+    {                          /* your method should at least insert marker. */
+
+    case MN:
+      add_word_args ("(%s)", marker);
+      if (first_footnote_this_node)
+       {
+         char *temp_string = xmalloc ((strlen (current_node))
+                                      + (strlen ("-Footnotes")) + 1);
+         add_word_args (" (*note %s-Footnotes::)", current_node);
+         strcpy (temp_string, current_node);
+         strcat (temp_string, "-Footnotes");
+         remember_node_reference (temp_string, line_number, followed_reference);
+         free (temp_string);
+         first_footnote_this_node = false;
+       }
+      break;
+
+    case BN:
+      add_word_args ("(%s)", marker);
+      break;
+
+    default:
+      break;
+    }
+  free (marker);
+  free (note);
+}
+
+/* Non-zero means that we are currently in the process of outputting
+   footnotes. */
+int already_outputting_pending_notes = 0;
+
+/* Output the footnotes.  We are at the end of the current node. */
+output_pending_notes ()
+{
+  FN *footnote = pending_notes;
+
+  if (!pending_notes)
+    return;
+
+  switch (footnote_style)
+    {
+
+    case MN:
+      {
+       char *old_current_node = current_node;
+       char *old_command = savestring (command);
+
+       already_outputting_pending_notes++;
+       execute_string ("@node %s-Footnotes,,,%s\n", current_node, current_node);
+       already_outputting_pending_notes--;
+       current_node = old_current_node;
+       free (command);
+       command = old_command;
+      }
+      break;
+
+    case BN:
+      close_paragraph ();
+      in_fixed_width_font++;
+      execute_string ("---------- Footnotes ----------\n\n");
+      in_fixed_width_font--;
+      break;
+    }
+
+  /* Handle the footnotes in reverse order. */
+  {
+    FN **array = (FN **) xmalloc ((footnote_count + 1) * sizeof (FN *));
+
+    array[footnote_count] = (FN *) NULL;
+
+    while (--footnote_count > -1)
+      {
+       array[footnote_count] = footnote;
+       footnote = footnote->next;
+      }
+
+    filling_enabled = true;
+    indented_fill = true;
+
+    while (footnote = array[++footnote_count])
+      {
+
+       switch (footnote_style)
+         {
+
+         case MN:
+         case BN:
+           execute_string ("(%s)  %s", footnote->marker, footnote->note);
+           close_paragraph ();
+           break;
+         }
+      }
+    close_paragraph ();
+    free (array);
+  }
+}
+\f
+/*
+ * Local variables:
+ * compile-command: "gcc -g -Bstatic -o makeinfo makeinfo.c getopt.c"
+ * end:
+ */
+