DataMuseum.dk

Presents historical artifacts from the history of:

DKUUG/EUUG Conference tapes

This is an automatic "excavation" of a thematic subset of
artifacts from Datamuseum.dk's BitArchive.

See our Wiki for more about DKUUG/EUUG Conference tapes

Excavated with: AutoArchaeologist - Free & Open Source Software.


top - metrics - download
Index: T m

⟦2267f5e7d⟧ TextFile

    Length: 96219 (0x177db)
    Types: TextFile
    Names: »makeinfo.c«

Derivation

└─⟦a05ed705a⟧ Bits:30007078 DKUUG GNU 2/12/89
    └─⟦92d325a64⟧ »./info.tar.Z« 
        └─⟦4a2cb6db9⟧ 
            └─⟦this⟧ »makeinfo.c« 

TextFile

/* makeinfo.c -- Program for converting texinfo format files into
   info files.

   Copyright (C) 1987 Free Software Foundation, Inc.
  
   This file is part of GNU Info.

   Makeinfo is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY.  No author or distributor accepts
   responsibility to anyone for the consequences of using it or for
   whether it serves any particular purpose or works at all, unless he
   says so in writing.  Refer to the GNU Emacs General Public License
   for full details.

   Everyone is granted permission to copy, modify and redistribute
   Makeinfo, but only under the conditions described in the GNU Emacs
   General Public License.   A copy of this license is supposed to
   have been given to you along with GNU Emacs so you can know your
   rights and responsibilities.  It should be in a file named COPYING.
   Among other things, the copyright notice and this notice must be
   preserved on all copies.

  Modification history:

  Initial author: Brian Fox
  April 88; added splitting
  June 88; fixed filling to do the right thing at sentence ends.
  	   stopped automatically removing filename extensions on output
	   split files.
  July 88; added code to handle un-closed braces at the start of a node,
           un-finished insertions at the start of a node,
	   and outputting the `header' at the start of each info split sub-file
  July 88; Made split_file NOT update the tags table to reflect the changes in
           the output files.  This means the reader has also changed.

  October 88;  Allowed user defined commands.  In the specific, allowed the
	       the user to make his/her own indices.  We define the default
	       ones.
*/

#include <stdio.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <ctype.h>
#include <pwd.h>

/* #define SUN4 */
#ifdef SUN4
#include <alloca.h>
#endif

/* Error levels */
#define NO_ERROR 0
#define SYNTAX 1
#define FATAL 2

/* Boolean values. */
#define true  1
#define false 0
typedef int boolean;

/* I want to make sure that NULL looks like this. */
#ifdef NULL
#undef NULL
#endif
#define NULL 0x0

/* How to allocate permanent storage for STRING. */
#define savestring(x) ((char *)strcpy (xmalloc (1 + strlen (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)

typedef int FUNCTION ();	/* So I can say FUNCTION *foo; */

#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. */

char *progname;			/* Global pointer to argv[0].  */

/* The current input file state. */
char *input_filename;
char *input_text;
int size_of_input_text;
int input_text_offset;
int line_number;

#define whitespace(c) (((c) == '\t') || ((c) == ' '))
#define sentence_ender(c) ((c) == '.' || (c) == '?' || (c) == '!')
#define cr_or_whitespace(c) (((c) == '\t') || ((c) == ' ') || ((c) == '\n'))
#define curchar() input_text[input_text_offset]

#define member(c, s) strchr (s, c)
#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. */
char *output_filename, *pretty_output_filename;
FILE *output_stream;
int output_position;		/* position in the output file. */

char *output_paragraph;		/* output paragraph buffer. */
int output_paragraph_offset;	/* offset into the output paragraph buffer. */
/* The output paragraph "cursor" horizontal position. */
int output_column = 0;
boolean paragraph_is_open = false; /* non-zero means output_paragraph contains text. */
#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;

/* 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/descreases 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;
     
/* 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. */
  int touched;			/* non-zero means this node has been referenced. */
} TAG_ENTRY;

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. */
typedef struct node_ref {
  struct node_ref *next;
  char *node;
  int line_no;
  enum reftype type;
} 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. */
     
boolean print_warnings = true;	/* Default is to notify users of bad choices. */
boolean validating = true;	/* Default is to check node references. */

/* 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;
     
/* 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 (), cm_chapter (), cm_unnumbered (), cm_appendix (), cm_section (),
cm_unnumberedsec (), cm_appendixsec (), cm_subsection (), cm_unnumberedsubsec (),
cm_appendixsubsec (), cm_subsubsection (), 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_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 ();

int do_nothing ();
int misplaced_brace (), cm_obsolete ();

/* Allocating storage. */
char *xmalloc ();

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[] = {
  {"@", insert_self, false},
  {"{", insert_self, false},
  {"}", insert_self, false},
  {"'", insert_self, false},
  {"`", insert_self, false},
  {":", cm_force_abbreviated_whitespace, false},
  {".", cm_force_sentence_end, false},
  {"?", cm_force_sentence_end, false},
  {"!", cm_force_sentence_end, false},
  {"*", cm_asterisk, false},
  {"defindex", cm_defindex, false},
  {"defcodeindex", cm_defindex, false},
  {"dots", cm_dots, true},
  {"bullet", cm_bullet, true},
  {"TeX", cm_TeX, true},
  {"tex", cm_tex, false},
  {"copyright", cm_copyright, true},
  {"code", cm_code, true},
  {"samp", cm_samp, true},
  {"file", cm_file, true},
  {"kbd", cm_kbd, true},
  {"key", cm_key, true},
  {"ctrl", cm_ctrl, true},
  {"var", cm_var, true},
  {"dfn", cm_dfn, true},
  {"emph", cm_emph, true},
  {"strong", cm_strong, true},
  {"cite", cm_cite, true},
  {"i", cm_italic, true},
  {"b", cm_bold, true},
  {"r", cm_roman, true},
  {"t", cm_title, true},
  {"w", cm_w, true},
  {"refill", cm_refill, false},
  {"chapter", cm_chapter, false},
  {"unnumbered", cm_unnumbered, false},
  {"appendix", cm_appendix, false},
  {"section", cm_section, false},
  {"unnumberedsec", cm_unnumberedsec, false},
  {"appendixsec",cm_appendixsec, false},
  {"subsection", cm_subsection, false},
  {"unnumberedsubsec", cm_unnumberedsubsec, false},
  {"appendixsubsec", cm_appendixsubsec, false},
  {"subsubsection", cm_subsubsection, false},
  {"ichapter", cm_chapter, false},
  {"iunnumbered", cm_unnumbered, false},
  {"iappendix", cm_appendix, false},
  {"isection", cm_section, false},
  {"iunnumberedsec", cm_unnumberedsec, false},
  {"iappendixsec",cm_appendixsec, false},
  {"isubsection", cm_subsection, false},
  {"iunnumberedsubsec", cm_unnumberedsubsec, false},
  {"iappendixsubsec", cm_appendixsubsec, false},
  {"isubsubsection", cm_subsubsection, false},
  {"node", cm_node, false},
  {"menu", cm_menu},
  {"xref", cm_xref, true},
  {"pxref", cm_pxref, true},
  {"inforef", cm_inforef, true},
  {"quotation", cm_quotation, false},
  {"example", cm_example, false},
  {"smallexample", cm_smallexample, false},
  {"lisp", cm_lisp, false},
  {"format", cm_format, false},
  {"display", cm_display, false},
  {"itemize", cm_itemize, false},
  {"enumerate", cm_enumerate, false},
  {"table", cm_table, false},
  {"itemx", cm_itemx, false},
  {"noindent", cm_noindent, false},
  {"setfilename", cm_setfilename, false},
  {"c", cm_comment, false},
  {"comment", cm_comment, false},
  {"ignore", cm_ignore, false},
  {"br", cm_br, false},
  {"sp", cm_sp, false},
  {"page", do_nothing, false},
  {"group", cm_group, false},
  {"need", cm_need, false},
  {"center", cm_center, false},
  {"include", cm_include, false},
  {"bye", cm_bye, false},
  {"item", cm_item, false},
  {"end", cm_end, false},
  {"ifinfo", cm_ifinfo, false},
  {"iftex", cm_iftex, false},
  {"titlepage", cm_titlepage, false},
  {"pindex", cm_pindex, false},
  {"findex", cm_findex, false},
  {"kindex", cm_kindex, false},
  {"cindex", cm_cindex, false},
  {"vindex", cm_vindex, false},
  {"tindex", cm_tindex, false},
  {"synindex", cm_synindex, false},
  {"syncodeindex", cm_synindex, false},
  {"settitle", cm_settitle, false},
  {"asis", cm_asis, true},
  {"setchapternewpage", cm_setchapternewpage, false},
  {"printindex", cm_printindex, false},
  {"minus", cm_minus, true},
  {"contents", do_nothing, false},
  {"summarycontents", do_nothing, false},
  {"exdent", cm_exdent, 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};

/* 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;
{
  int arg_index = 1;
  progname = argv[0];
  
  if (argc == 1) usage ();	/* tell this luser how to use */

  /* Parse argument flags from the input line. */

  while (arg_index != argc && *(argv[arg_index]) == '-') {

    /* There are flag arguments, so parse them */
    char *the_arg = (argv[arg_index])+1;

    if (strcmp (the_arg, "v") == 0) validating = true;
    if (strcmp (the_arg, "nv") == 0) validating = false;

    if (strcmp (the_arg, "w") == 0) print_warnings = true;
    if (strcmp (the_arg, "nw") == 0) print_warnings = false;

    if (strcmp (the_arg, "s") == 0) splitting = true;
    if (strcmp (the_arg, "ns") == 0) splitting = false;

    if (strcmp (the_arg, "fc") == 0) {
      /* Set fill_column to the value of the next arg. */
      if (!(argv[++arg_index]) ||
	  (sscanf (argv[arg_index], "%d", &fill_column) != 1))
	 usage ();
    }

    if (strcmp (the_arg, "el") == 0) {
      if (!(argv[++arg_index]) ||
	  (sscanf (argv[arg_index], "%d", &max_error_level) != 1))
	 usage ();
    }

    if (strcmp (the_arg, "rl") == 0) {
      if (!(argv[++arg_index]) ||
	  (sscanf (argv[arg_index], "%d", &reference_warning_limit) != 1))
	 usage ();
    }

    if (strcmp (the_arg, "ft") == 0) {
      if (argv[++arg_index]) {
	set_footnote_style (argv[arg_index]);
      }	else usage ();
    }
    arg_index++;
  }
  while (arg_index != argc) convert (argv[arg_index++]);
}

usage ()
{
  /* Tell the luser how to use this program. */
  fprintf (stderr,
"Usage: %s [-w -v -fc [num] -rl [num] -el [num] -ft [style]] [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\
Use `-nv' to suppress node cross reference validation\n\
    `-nw' to suppress warning messages (errors are still output)\n\
    `-ns' to suppress the splitting of large files\n\
\n\
`-fc NUM' sets the filling column to NUM (default %d).\n\
`-el NUM' sets the error limit to NUM (default %d).\n\
`-rl NUM' sets the reference warning limit to NUM (default %d).\n\
`-ft STYLE' sets footnote style to STYLE.  STYLE should either be `MN'\n\
     for `make node', or `BN' for `bottom node'.\n\n",
	   progname, fill_column, max_error_level, reference_warning_limit);
  exit (2);
}

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);
}

/* 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;
  char *result;

  if ((stat (filename, &fileinfo)) != 0) return ((char *)NULL);
  
  file = open (filename, O_RDONLY);
  if (file < 0) return ((char *)NULL);

  /* Load the file. */
  result = xmalloc (fileinfo.st_size);
  if (read (file, result, fileinfo.st_size) != fileinfo.st_size)
    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);
  line_number = input_text_offset = 0;
  return (result);
}

/* 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. */
int xrealloc (pointer, nbytes)
     char *pointer;
     int nbytes;
{
  pointer = (char *)realloc (pointer, nbytes);
  if (!pointer) {
    error ("Virtual memory exhausted in realloc ().");
    abort ();
  }
  return ((int)pointer);
}
  
/* 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;
}

/* 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);
}

/* Flush all open files on the file stack. */
flush_file_stack ()
{
  while (filestack) {
    free (input_filename);
    free (input_text);
    popfile ();
  }
}


/* **************************************************************** */
/*								    */
/*			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;
{
  int index = input_text_offset;
  int i;
  
  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 ()
{
  if (!errors_printed++) fprintf (stderr, "Errors from %s:\n", progname);
  if (max_error_level && (errors_printed > max_error_level)) {
    fprintf (stderr, "Too many errors!  Gave up.");
    flush_file_stack ();
    cm_bye ();
  }
}

/* Return the next token as a string pointer.  We cons the
   string.  Ugh. */
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, "{}:.@*'`,!?;"));
}

/* 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)
      return (line_error ("Expected `%s'", string));
  } 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;
}

/* 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. */
    return (int) fs_error (name);
  } 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);

  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. */
      validate_file (real_output_filename, tag_table);
    }
    if (splitting && !errors_printed)
      split_file (real_output_filename, 0);
  }
}

/* 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;
{
  char *result;
  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 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';
	
	if (!(user_entry = getpwnam (username))) {
	  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));
  }
}
  
free_and_clear (pointer)
     char **pointer;
{
  if ((*pointer) != (char *)NULL) {
    free (*pointer);
    *pointer = (char *)NULL;
  }
}

init_internals ()
{
  /* Initialize some state. */

  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 (character == '-') {
      dash_count++;
      if (dash_count == 3) {
	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 ();
      }
    }

    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;
  BRACE_ELEMENT *next;
  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 ();
	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 it. */
	      {
		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 += (paragraph_buffer_len / 2)));
  }
}

/* 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)
      indent (output_column = current_indent);
  } 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,
		       bad_type};

char *insertion_type_names[] = {
  "menu", "quotation", "lisp", "example", "smallexample", "display",
  "itemize", "format", "enumerate", "table", "group", "ifinfo"
};

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;
} 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 ()
{
  int i;
  char *current_item_function;
  get_until ("\n", &current_item_function);
  canon_white (current_item_function);
  return (current_item_function);
}
      
push_insertion (type, item_function)
     enum insertion_type type;
     char *item_function;
{
  /* Push the state of the current insertion on the stack. */
  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->next = insertion_stack;
  insertion_stack = new;
  insertion_level++;
}

pop_insertion ()
{
  /* Pop the value on top of the insertion stack into the
     global variables. */
  INSERTION_ELT *temp = insertion_stack;
  if (temp == (INSERTION_ELT *)NULL) return;
  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--;
}

char *
insertion_type_pname (type)
     enum insertion_type type;
{
  /* Return a pointer to the print name of this
     enumerated type. */
  if ((int)type < (int)bad_type) return (insertion_type_names[(int)type]);
  else return ("Broken-Type in insertion_type_pname");
}

enum insertion_type
find_type_from_name (name)
     char *name;
{
  /* Return the insertion_type associated with NAME.
     If the type is not one of the known ones, return BAD_TYPE. */
  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 ()
{}

/* 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;
{
  close_paragraph ();
  push_insertion (type, get_item_function ());
  filling_enabled = false;		/* the general case for insertions. */
  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:
    indented_fill = filling_enabled = true;
    current_indent += default_indentation_increment;
    break;

    /* Just like @example, but no indentation. */
  case format:
    break;

  case display:
  case example:
  case smallexample:
  case lisp:
    current_indent += default_indentation_increment;
    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:
    current_indent += default_indentation_increment;
    start_numbering (1);
    filling_enabled = indented_fill = true;
    break;

  case group:
    break;

  case ifinfo:
    /* Undo whatever we just did.  This is a no-op. */
    filling_enabled = insertion_stack->filling_enabled;
    indented_fill = insertion_stack->indented_fill;
    break;
  }
  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 itemize:
  case table:
  case example:
  case smallexample:
  case lisp:
  case display:
  case quotation:
    current_indent -= default_indentation_increment;
    break;

  case enumerate:
    stop_numbering ();
    current_indent -= default_indentation_increment;
    break;

  case group:
  case ifinfo:
  case format:
    break;			/* C perversely requires this. */
  }
  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 {
      line_number = insertion_stack->line_number;
      line_error ("missing `%cend %s'", COMMAND_PREFIX,
		  insertion_type_pname (insertion_stack->insertion));
      pop_insertion ();
    }
  }
  line_number = real_line_number;
}

#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;
  }
}
  
number_item ()
{
  /* Place a number into the output stream. */
  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. */

insert_self ()
{
  /* Commands which insert themselves. */
  add_word (command);
}

cm_asterisk ()			/* force line break */
{
  /* Force a line break in the output. */
  insert ('\n');
  indent (output_column = current_indent);
}

cm_dots (arg)
     int arg;
{
  /* Insert ellipsis. */
  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 ('-');
}

cm_TeX (arg)
     int arg;
{
  /* Insert "TeX". */
  if (arg == START) add_word ("TeX");
}

cm_copyright (arg)
     int arg;
{
  if (arg == START) add_word ("(C)");
}

cm_code (arg)
     int arg;
{
  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;
{}

cm_ctrl (arg, position)
     int arg, position;
{
  if (arg == END) 
    /* convert the character at position-1 into CTL. */
    output_paragraph[position - 1] = CTL(output_paragraph[position - 1]);    
}

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;
  char *temp;

  close_paragraph ();
  filling_enabled = indented_fill = false;
  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;
}

cm_chapter ()
{
  insert_and_underscore ('*');
}

cm_unnumbered ()
{
  cm_chapter ();
}

cm_appendix ()
{
  cm_chapter ();
}

cm_section ()
{
  insert_and_underscore ('=');
}

cm_unnumberedsec ()
{
  cm_section ();
}

cm_appendixsec ()
{
  cm_section ();
}

cm_subsection ()
{
  insert_and_underscore ('-');
}

cm_unnumberedsubsec ()
{
  cm_subsection ();
}

cm_appendixsubsec ()
{
  cm_subsection ();
}

cm_subsubsection ()
{
  insert_and_underscore ('.');
}


/* **************************************************************** */
/*								    */
/*		   Adding nodes, and making tags		    */
/*								    */
/* **************************************************************** */

init_tag_table ()
{
  /* Start a new 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;

  close_paragraph ();
  filling_enabled = false;
  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);
  return (string);
}

TAG_ENTRY *
find_node (name)
     char *name;
{
  /* Look up NAME in the tag table, and return the associated
     tag_entry.  If the node is not in the table, then return -1.
     */

  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 (node, prev, next, up, position, line_no)
     char *node, *prev, *next, *up;
     int position, line_no;
{
  /* Remember NODE and associates. */
  if (validating) {
    /* Check for existance of this tag already. */
    if (find_node (node)) {
      line_error ("The node `%s' already exists", node);
      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->touched = 0;		/* not yet referenced. */
    new->next_ent = tag_table;
    tag_table = new;
  }
}

/* The order is: nodename, nextnode, prevnode, upnode. */
cm_node ()
{
  char *node, *prev, *next, *up;
  int new_node_pos;
  extern int already_outputting_pending_notes;

  /* 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 ();

  /* ??? 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. */
  add_word_args ("\n\037\nFile: %s,  Node: %s", pretty_output_filename, node);

  if (strlen (next)) add_word_args (",  Next: %s", next);
  if (strlen (prev)) add_word_args (",  Prev: %s", prev);
  if (strlen (up))   add_word_args (",  Up: %s", up);

  insert ('\n');
  close_paragraph ();

  if (strlen (node) == 0) {
    line_error ("No node name specified for `@%s' command", command);
    free (node);
    free (next);
    free (prev);
    free (up);
  } else
    remember_node (node, prev, next, up, new_node_pos, line_number);
  filling_enabled = true;
}

/* Validation of an info file.  We must already have the tag table? */
validate_file (filename, tag_table)
     char *filename;
     TAG_ENTRY *tag_table;
{
  /* 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 "(DIR)".  Maybe we should check for "Next" nodes
     pointing back with "Prev". Nahh. */

  TAG_ENTRY *tags = tag_table;
  int count = 0;
  
  while (tags != (TAG_ENTRY *)NULL) {
    count++;
    validate (tags->next, tags->line_no, "Next");
    validate (tags->prev, tags->line_no, "Prev");
    validate (tags->up, tags->line_no, "Up");
    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) {
      line_number = tags->line_no;
      warning ("Node `%s' has been referenced %d times", tags->node, tags->touched);
    }
    if (tags->touched == 0) {
      /* Notice that the node "Top" is special, and doesn't have to
	 be referenced. */
      if (stricmp (tags->node, "Top") != 0) {
	line_number = tags->line_no;
	warning ("Unreferenced node `%s'", tags->node);
      }
    }
    tags = tags->next_ent;
  }
}

validate (tag, line, label)
     char *tag;
     int line;
     char *label;
{
  TAG_ENTRY *result;
  if (!tag || !*tag) return;
  result = find_node (tag);
  if (!result) {
    if (*tag == '(') return;	/* tag defined in another file. */
    line_number = line;
    line_error ("Validation error.  `%s' field points to node `%s', which doesn't exist",
		label, tag);
  } else result->touched++;
}

/* 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, first_file = 1;
    TAG_ENTRY *tags = tag_table;
    TAG_ENTRY *temp = (TAG_ENTRY *)NULL;
    char *indirect_info = (char *)0x00;

    /* 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;

      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;
	  tags = tags->next_ent;
	  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));

#ifdef MAKE_TAG_POINTERS_BE_DIRECT
	    /* Keep track of the starts of new files. */
	    if (tags) tags->touched = -99;
#endif /* MAKE_TAG_POINTERS_BE_DIRECT */

	    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);
	      exit (2);
	    }

	    if (!indirect_info) {
	      indirect_info = the_file + file_top;
	      sprintf (indirect_info, "\037\nIndirect:\n");
	      indirect_info += strlen (indirect_info);
	    }

#ifdef MAKE_TAG_POINTERS_BE_DIRECT
	    sprintf (indirect_info,
		     "%s-%d: %d\n",
		     root_filename, which_file,
		     file_top + (header_size * (which_file - 1)));
#else
	    sprintf (indirect_info, "%s-%d: %d\n",
		     root_filename, which_file, file_top);
#endif  /* MAKE_TAG_POINTERS_BE_DIRECT */

	    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 (2);
    }

    {
      int distance = indirect_info - the_file;
      fwrite (the_file, 1, distance, output_stream);

#ifdef MAKE_TAG_POINTERS_BE_DIRECT
      /* Output the tag table, but remember to update each pointer according
	 to how many headers come before it in the output file. */
      tags = tag_table;
      which_file = 1;
      while (tags) {
	if (tags->touched == -99)
	  which_file++;
	tags->position += (header_size * which_file);
	tags = tags->next_ent;
      }
#endif /* MAKE_TAG_POINTERS_BE_DIRECT */
      
      /* 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 the note_and_menu_references
   list 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_node_reference (node, line, type)
     char *node;
     int line;
     enum reftype type;
{
  /* Remember this node name for later validation use. */
  NODE_REF *temp = (NODE_REF *)xmalloc (sizeof (NODE_REF));

  temp->next = node_references;
  temp->node = savestring (node);
  temp->line_no = line;
  temp->type = type;

  node_references = temp;
}

validate_other_references (ref_list)
     NODE_REF *ref_list;
{
  while (ref_list != (NODE_REF *)NULL) {
    validate (ref_list->node, ref_list->line_no,
	      reftype_type_string (ref_list->type));
    ref_list = ref_list->next;
  }
}

free_node_references ()
{
  NODE_REF *temp = node_references;

  while (node_references != (NODE_REF *)NULL) {
    temp = node_references;
    free (node_references->node);
    node_references = node_references->next;
    free (temp);
  }
}

#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:
  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);
  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) {
      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)
{
  if (arg == START) {
    px_ref_flag++;
    cm_xref (arg);
    px_ref_flag--;
  }
  else add_char ('.');
}

cm_inforef (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_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;
{
  char *temp_string = xmalloc (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 ();

  /* Don't FREE temp_string here.  The code at reader_loop does it. */
  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 ()
{
  BRACE_ELEMENT *temp_brace = brace_stack;
  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:
      {
	int temp_point = input_text_offset;
	
	kill_self_indent (-1); /* get rid of extra characters */

	/* 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);
  }
}

cm_end ()
{
  /* End existing insertion block. */
  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);
}

/* 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 ();
}

cm_sp ()
{
  /* Insert the number of blank lines passed as argument. */
  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 (' ');
  }
  add_word_args ("%s", line);
  free (line);
  insert ('\n');
  close_paragraph ();
  filling_enabled = true;
}

/* 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 ();
}

cm_infoinclude ()
{
  /* Remember this file, and move onto the next. */

  close_paragraph ();
  pushfile ();
  get_rest_of_line (&input_filename);

  if (!find_and_load (input_filename)) {
    char *error_file = savestring (input_filename);
    popfile ();
    /* Cannot "@include foo", in line 5 of "/wh/bar". */
    line_error ("`@%s %s' failed", command, error_file);
    fs_error (error_file);
    free (error_file);
  }
}

misplaced_brace ()
{
  /* The other side of a malformed expression. */
  line_error ("Misplaced `}'");
}

cm_force_abbreviated_whitespace ()
{
  /* Don't let the filling algorithm insert extra whitespace here. */
}

cm_force_sentence_end ()
{
  /* Make the output paragraph end the sentence here, even though it
     looks like it shouldn't.  This also inserts the character which invoked it. */
  add_char (META ((*command)));
}

cm_bye ()
{
  /* Signals end of processing.  Easy to make this happen. */
 input_text_offset = size_of_input_text;
}

cm_asis ()
{}

cm_setchapternewpage ()
{
  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. */
} INDEX_ELT;

/* A list of short-names for each index, and the index to that index in our
   index array, the_indices.  (Sorry, I couldn't resist.) */
typedef struct {
  char *name;
  int index;
} 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");
  defindex ("fn");
  defindex ("cp");
  defindex ("vr");
  defindex ("tp");
  defindex ("ky");
}

/* Find which element in the known list of indices has this name.
   Returns -1 if NAME isn't found. */
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 -1 if the index doesn't exist. */
INDEX_ALIST *
find_index (name)
     char *name;
{
  int offset = find_index_offset (name);
  if (offset > 0)
    return (name_index_alist[offset]);
  else return ((INDEX_ALIST *) -1);
}

/* 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 ((int)which > -1)
    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. */
defindex (name)
     char *name;
{
  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;

  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 = translate_index (name);
  char *index_entry;
  
  close_paragraph ();
  get_rest_of_line (&index_entry);
  
  if (which < 0) {
    line_error ("Unknown index reference `%d'", name);
    return (-1);
  } else {
    INDEX_ELT *new = (INDEX_ELT *)xmalloc (sizeof (INDEX_ELT));
    new->next = the_indices[which];
    new->entry = index_entry;
    new->node = current_node;
    the_indices[which] = new;
  }
  return (0);
}

#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 ()
{
  char *name;
  get_rest_of_line (&name);
  
  if ((int)find_index (name) > -1) {
    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);
    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 ("dt");
}

/* Sorting the index. */
index_element_compare (element1, element2)
     INDEX_ELT **element1, **element2;
{
  return (strcmp ((*element1)->entry, (*element2)->entry));
}

INDEX_ELT **
sort_index (index)
     INDEX_ELT *index;
{
  /* 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 *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);
}

/* 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;
  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);

  close_paragraph ();
  filling_enabled = false;
  add_word ("* Menu:\n\n");
  
  for (item = 0; (index = array[item]); item++) {
    execute_string ("* %s: %s.\n", index->entry, index->node);
    flush_output ();
  }
  free (array);
  close_paragraph ();
  filling_enabled = previous_filling_enabled_value;
}


/* **************************************************************** */
/*								    */
/*		    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_footnote_style (string)
     char *string;
{
  /* Set the footnote style based on he style identifier in 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. */

cm_footnote ()
{
  /* Handle a "footnote".
     footnote *{this is a footnote}
     where "*" is the marker character for this note. */

  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);
}

/* Output the footnotes.  We are at the end of the current node. */
int already_outputting_pending_notes = 0; /* excuse me. */
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 ();
    execute_string ("---------- Footnotes ----------\n\n");
    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 -o makeinfo makeinfo.c"
 * end:
 */