/* Makeinfo -- convert Texinfo source files into Info files.
   $Id: makeinfo.c,v 1.11 1998/07/06 21:58:00 law Exp $

   Copyright (C) 1987, 92, 93, 94, 95, 96, 97, 98
   Free Software Foundation, Inc.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

   Makeinfo was authored by Brian Fox (bfox@ai.mit.edu). */

/* Indent #pragma so that older Cpp's don't try to parse it. */
#ifdef _AIX
 #pragma alloca
#endif /* _AIX */

int major_version = 1;
int minor_version = 68;

#include "system.h"
#include "getopt.h"

#ifdef TM_IN_SYS_TIME
#include <sys/time.h>
#else
#include <time.h>
#endif /* !TM_IN_SYS_TIME */

#ifdef __GNUC__
# undef alloca
# define alloca __builtin_alloca
#else
# ifdef HAVE_ALLOCA_H
#  include <alloca.h>
# else
#  ifndef _AIX
char *alloca ();
#  endif
# endif
#endif

/* We'd like to take advantage of _doprnt if it's around, a la error.c,
   but then we'd have no VA_SPRINTF.  */
#if HAVE_VPRINTF
# if __STDC__
#  include <stdarg.h>
#  define VA_START(args, lastarg) va_start(args, lastarg)
# else
#  include <varargs.h>
#  define VA_START(args, lastarg) va_start(args)
# endif
# define VA_FPRINTF(file, fmt, ap) vfprintf (file, fmt, ap)
# define VA_SPRINTF(str, fmt, ap) vsprintf (str, fmt, ap)
#else /* not HAVE_VPRINTF */
# define VA_START(args, lastarg)
# define va_alist a1, a2, a3, a4, a5, a6, a7, a8
# define va_dcl char *a1, *a2, *a3, *a4, *a5, *a6, *a7, *a8;
# define va_end(args)
#endif

/* You can change some of the behavior of Makeinfo by changing the
   following defines: */

/* Define INDENT_PARAGRAPHS_IN_TABLE if you want the paragraphs which
   appear within an @table, @ftable, or @itemize environment to have
   standard paragraph indentation.  Without this, such paragraphs have
   no starting indentation. */
/* #define INDENT_PARAGRAPHS_IN_TABLE */

/* Define DEFAULT_INDENTATION_INCREMENT as an integer which is the amount
   that @example should increase indentation by.  This incremement is used
   for all insertions which indent the enclosed text. */
#define DEFAULT_INDENTATION_INCREMENT 5

/* Define PARAGRAPH_START_INDENT to be the amount of indentation that
   the first lines of paragraphs receive by default, where no other
   value has been specified.  Users can change this value on the command
   line, with the --paragraph-indent option, or within the texinfo file,
   with the @paragraphindent command. */
#define PARAGRAPH_START_INDENT 3

/* Define DEFAULT_PARAGRAPH_SPACING as the number of blank lines that you
   wish to appear between paragraphs.  A value of 1 creates a single blank
   line between paragraphs.  Paragraphs are defined by 2 or more consecutive
   newlines in the input file (i.e., one or more blank lines). */
#define DEFAULT_PARAGRAPH_SPACING 1

/* Define HAVE_MACROS to enable the macro facility of Texinfo.  Using this
   facility, users can create their own command procedures with
   arguments.   Must always be defined.  */
#define HAVE_MACROS


#define COMPILING_MAKEINFO
#include "makeinfo.h"

/* Nonzero means that we are currently hacking the insides of an
   insertion which would use a fixed width font. */
static int in_fixed_width_font = 0;

/* Nonzero means that start_paragraph () MUST be called before we pay
   any attention to close_paragraph () calls. */
int must_start_paragraph = 0;

/* Nonzero means a string is in execution, as opposed to a file. */
static int executing_string = 0;

/* Nonzero means a macro string is in execution, as opposed to a file. */
static int me_executing_string = 0;

#if defined (HAVE_MACROS)
/* If non-NULL, this is an output stream to write the full macro expansion
   of the input text to.  The result is another texinfo file, but
   missing @include, @infoinclude, @macro, and macro invocations.  Instead,
   all of the text is placed within the file. */
FILE *macro_expansion_output_stream = (FILE *)NULL;
char *macro_expansion_filename;

/* Here is a structure used to remember input text strings and offsets
   within them. */
typedef struct {
  char *pointer;                /* Pointer to the input text. */
  int offset;                   /* Offset of the last character output. */
} ITEXT;

static ITEXT **itext_info = (ITEXT **)NULL;
static int itext_size = 0;

/* Nonzero means to inhibit writing macro expansions to the output
   stream, because it has already been written. */
int me_inhibit_expansion = 0;

ITEXT *remember_itext ();
void forget_itext (), me_append_before_this_command ();
void append_to_expansion_output (), write_region_to_macro_output ();
void maybe_write_itext (), me_execute_string ();
#endif /* HAVE_MACROS */


/* **************************************************************** */
/*                                                                  */
/*                          Global Variables                        */
/*                                                                  */
/* **************************************************************** */

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

/* Return nonzero if STRING is the text at input_text + input_text_offset,
   else zero. */
#define looking_at(string) \
  (strncmp (input_text + input_text_offset, string, strlen (string)) == 0)

/* And writing to the output. */

/* The output file name. */
char *output_filename = (char *)NULL;
char *pretty_output_filename;

/* Name of the output file that the user elected to pass on the command line.
   Such a name overrides any name found with the @setfilename command. */
char *command_output_filename = (char *)NULL;

/* A colon separated list of directories to search for files included
   with @include.  This can be controlled with the `-I' option to makeinfo. */
char *include_files_path = (char *)NULL;

/* Position in the output file. */
int output_position;

#define INITIAL_PARAGRAPH_SPACE 5000
int paragraph_buffer_len = INITIAL_PARAGRAPH_SPACE;

/* Nonzero indicates that filling will take place on long lines. */
int filling_enabled = 1;

/* Nonzero means that words are not to be split, even in long lines.  This
   gets changed for cm_w (). */
int non_splitting_words = 0;

/* Nonzero indicates that filling a line also indents the new line. */
int indented_fill = 0;

/* The amount of indentation to add at the starts of paragraphs.
   0 means don't change existing indentation at paragraph starts.
   > 0 is amount to indent new paragraphs by.
   < 0 means indent to column zero by removing indentation if necessary.

   This is normally zero, but some people prefer paragraph starts to be
   somewhat more indented than paragraph bodies.  A pretty value for
   this is 3. */
int paragraph_start_indent = PARAGRAPH_START_INDENT;

/* Nonzero means that the use of paragraph_start_indent is inhibited.
   @example uses this to line up the left columns of the example text.
   A negative value for this variable is incremented each time it is used.
   @noindent uses this to inhibit indentation for a single paragraph.  */
int inhibit_paragraph_indentation = 0;

/* Indentation that is pending insertion.  We have this for hacking lines
   which look blank, but contain whitespace.  We want to treat those as
   blank lines. */
int pending_indent = 0;

/* The amount that indentation increases/decreases by. */
int default_indentation_increment = DEFAULT_INDENTATION_INCREMENT;

/* Nonzero indicates that indentation is temporarily turned off. */
int no_indent = 1;

/* Nonzero means forcing output text to be flushright. */
int force_flush_right = 0;

/* Nonzero means that the footnote style for this document was set on
   the command line, which overrides any other settings. */
int footnote_style_preset = 0;

/* Nonzero means that we automatically number footnotes that have no
   specified marker. */
int number_footnotes = 1;

/* The current footnote number in this node.  Each time a new node is
   started this is reset to 1. */
int current_footnote_number = 1;

/* 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 search string which is used to find a line defining a node. */
char node_search_string[] =
  { '\n', COMMAND_PREFIX, 'n', 'o', 'd', 'e', ' ', 0 };

/* A search string which is used to find a line defining a menu. */
char menu_search_string[] =
  { '\n', COMMAND_PREFIX, 'm', 'e', 'n', 'u', 0 };

/* A search string which is used to find the first @setfilename. */
char setfilename_search[] =
  { COMMAND_PREFIX,
      's', 'e', 't', 'f', 'i', 'l', 'e', 'n', 'a', 'm', 'e', 0 };

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

/* The current nodes section level. */
int current_section = 0;

/* The filename of the current input file.  This is never freed. */
char *node_filename = (char *)NULL;

/* What we remember for each node. */
typedef struct tentry
{
  struct tentry *next_ent;
  char *node;           /* name of this node. */
  char *prev;           /* name of "Prev:" for this node. */
  char *next;           /* name of "Next:" for this node. */
  char *up;             /* name of "Up:" for this node.   */
  int position;         /* output file position of this node. */
  int line_no;          /* defining line in source file. */
  char *filename;       /* The file that this node was found in. */
  int touched;          /* Nonzero means this node has been referenced. */
  int flags;            /* Room for growth.  Right now, contains 1 bit. */
} TAG_ENTRY;

/* If node-a has a "Next" for node-b, but node-b has no "Prev" for node-a,
   we turn on this flag bit in node-b's tag entry.  This means that when
   it is time to validate node-b, we don't report an additional error
   if there was no "Prev" field. */
#define PREV_ERROR 0x1
#define NEXT_ERROR 0x2
#define UP_ERROR   0x4
#define NO_WARN    0x8
#define IS_TOP     0x10

TAG_ENTRY *tag_table = (TAG_ENTRY *) NULL;

/* Values for calling handle_variable_internal (). */
#define SET     1
#define CLEAR   2
#define IFSET   3
#define IFCLEAR 4

#if defined (HAVE_MACROS)
#define ME_RECURSE      0x01
#define ME_QUOTE_ARG    0x02

/* Macro definitions for user-defined commands. */
typedef struct {
  char *name;                   /* Name of the macro. */
  char **arglist;               /* Args to replace when executing. */
  char *body;                   /* Macro body. */
  char *source_file;            /* File where this macro is defined. */
  int source_lineno;            /* Line number within FILENAME. */
  int inhibited;                /* Nonzero means make find_macro () fail. */
  int flags;                    /* ME_RECURSE, ME_QUOTE_ARG, etc. */
} MACRO_DEF;

void add_macro (), execute_macro ();
MACRO_DEF *find_macro (), *delete_macro ();
#endif /* HAVE_MACROS */

/* Menu reference, *note reference, and validation hacking. */

/* The various references that we know about. */
enum reftype
{
  menu_reference, followed_reference
};

/* A structure to remember references with.  A reference to a node is
   either an entry in a menu, or a cross-reference made with [px]ref. */
typedef struct node_ref
{
  struct node_ref *next;
  char *node;                   /* Name of node referred to. */
  char *containing_node;        /* Name of node containing this reference. */
  int line_no;                  /* Line number where the reference occurs. */
  int section;                  /* Section level where the reference occurs. */
  char *filename;               /* Name of file where the reference occurs. */
  enum reftype type;            /* Type of reference, either menu or note. */
} NODE_REF;

/* The linked list of such structures. */
NODE_REF *node_references = (NODE_REF *) NULL;

/* Flag which tells us whether to examine menu lines or not. */
int in_menu = 0;

/* Flag which tells us how to examine menu lines. */
int in_detailmenu = 0;

/* Nonzero means that we have seen "@top" once already. */
int top_node_seen = 0;

/* Nonzero means that we have seen a non-"@top" node already. */
int non_top_node_seen = 0;

/* Flags controlling the operation of the program. */

/* Default is to remove output if there were errors.  */
int force = 0;

/* Default is to notify users of bad choices. */
int print_warnings = 1;

/* Default is to check node references. */
int validating = 1;

/* Nonzero means do not output "Node: Foo" for node separations. */
int no_headers = 0;

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

/* Nonzero means print out information about what is going on when it
   is going on. */
int verbose_mode = 0;

/* Nonzero means to be relaxed about the input file.  This is useful when
   we can successfully format the input, but it doesn't strictly match our
   somewhat pedantic ideas of correctness.  Right now, it affects what
   @table and @itemize do without arguments. */
int allow_lax_format = 0;

/* The list of commands that we hack in texinfo.  Each one
   has an associated function.  When the command is encountered in the
   text, the associated function is called with START as the argument.
   If the function expects arguments in braces, it remembers itself on
   the stack.  When the corresponding close brace is encountered, the
   function is called with END as the argument. */

#define START 0
#define END 1

typedef struct brace_element
{
  struct brace_element *next;
  COMMAND_FUNCTION *proc;
  int pos, line;
  int in_fixed_width_font;
} BRACE_ELEMENT;

BRACE_ELEMENT *brace_stack = (BRACE_ELEMENT *) NULL;

extern void do_multitable ();

void print_version_info ();
void usage ();
void push_node_filename (), pop_node_filename ();
void remember_error (), flush_file_stack ();
void convert_from_stream (), convert_from_file (), convert_from_loaded_file ();
void init_internals (), init_paragraph (), init_brace_stack ();
void init_insertion_stack (), init_indices ();
void init_tag_table (), write_tag_table (), write_tag_table_internal ();
void validate_file (), validate_other_references (), split_file ();
void free_node_references (), do_enumeration (), handle_variable ();
void handle_variable_internal ();
void normalize_node_name ();
void undefindex (), top_defindex (), gen_defindex ();
void define_user_command ();
void free_pending_notes (), output_pending_notes ();

char **get_brace_args ();
char *expansion ();
int array_len ();
void free_array ();
static int end_of_sentence_p ();
static void isolate_nodename ();
void reader_loop (), read_command ();
void remember_brace (), remember_brace_1 ();
void pop_and_call_brace (), discard_braces ();
void add_word (), add_char (), insert (), flush_output ();
void insert_string ();
void close_paragraph_with_lines (), close_paragraph ();
void ignore_blank_line ();
void do_flush_right_indentation (), discard_insertions ();
void start_paragraph (), indent ();
#if defined (VA_FPRINTF) && __STDC__
/* Unfortunately we must use prototypes if we are to use <stdarg.h>.  */
void add_word_args (char *, ...);
void execute_string (char *, ...);
#else
void add_word_args ();
void execute_string ();
#endif /* will not use prototypes */

void insert_self (), insert_space (), cm_ignore_line ();

void
  cm_TeX (), cm_asterisk (), cm_bullet (), cm_cite (),
  cm_code (), cm_copyright (), cm_ctrl (), cm_dfn (), cm_dircategory (),
  cm_direntry (), cm_dots (), cm_emph (), cm_enddots (),
  cm_kbd (), cm_key (), cm_no_op (), cm_no_op_line_arg (),
  cm_not_fixed_width (), cm_strong (), cm_var_sc (), cm_w (), cm_image ();

/* Sectioning.  */
void
  cm_chapter (), cm_unnumbered (), cm_appendix (), cm_top (),
  cm_section (), cm_unnumberedsec (), cm_appendixsec (),
  cm_subsection (), cm_unnumberedsubsec (), cm_appendixsubsec (),
  cm_subsubsection (), cm_unnumberedsubsubsec (), cm_appendixsubsubsec (),
  cm_heading (), cm_chapheading (), cm_subheading (), cm_subsubheading (),
  cm_majorheading (), cm_raisesections (), cm_lowersections ();

/* All @def... commands map to cm_defun, most accent commands map to
   cm_accent, most non-English letters map to cm_special_char.  */
void cm_defun (), cm_accent (), cm_special_char (), cm_dotless ();

void
  cm_node (), cm_menu (), cm_xref (), cm_ftable (), cm_vtable (), cm_pxref (),
  cm_inforef (), cm_uref (), cm_email (), cm_quotation (),
  cm_display (), cm_itemize (),
  cm_enumerate (), cm_tab (), cm_table (), cm_itemx (), cm_noindent (),
  cm_setfilename (), cm_br (), cm_sp (), cm_page (), cm_group (),
  cm_center (), cm_include (), cm_bye (), cm_item (), cm_end (),
  cm_ifinfo (), cm_ifnothtml (), cm_ifnottex (), cm_kindex (), cm_cindex (),
  cm_findex (), cm_pindex (), cm_vindex (), cm_tindex (),
  cm_synindex (), cm_printindex (), cm_minus (), cm_footnote (),
  cm_example (), cm_smallexample (), cm_lisp (), cm_format (), cm_exdent (),
  cm_defindex (), cm_defcodeindex (), cm_result (), cm_expansion (),
  cm_equiv (), cm_print (), cm_error (), cm_point (), cm_today (),
  cm_flushleft (), cm_flushright (), cm_smalllisp (), cm_finalout (),
  cm_cartouche (), cm_detailmenu (), cm_multitable ();

/* Conditionals. */
void cm_set (), cm_clear (), cm_ifset (), cm_ifclear ();
void cm_value (), cm_ifeq ();

#if defined (HAVE_MACROS)
/* Define a user-defined command which is simple substitution. */
void cm_macro (), cm_unmacro ();
#endif /* HAVE_MACROS */

/* Options. */
void cm_paragraphindent (), cm_footnotestyle ();

/* Internals. */
void command_name_condition (), misplaced_brace (), cm_obsolete (),
     cm_ideprecated ();

typedef struct
{
  char *name;
  COMMAND_FUNCTION *proc;
  int argument_in_braces;
} COMMAND;

/* Stuff for defining commands on the fly. */
COMMAND **user_command_array = (COMMAND **) NULL;
int user_command_array_len = 0;

#define NO_BRACE_ARGS 0
#define BRACE_ARGS 1

static COMMAND command_table[] = {
  { "\t", insert_space, NO_BRACE_ARGS },
  { "\n", insert_space, NO_BRACE_ARGS },
  { " ", insert_self, NO_BRACE_ARGS },
  { "!", insert_self, NO_BRACE_ARGS },
  { "\"", insert_self, NO_BRACE_ARGS },
  { "'", insert_self, NO_BRACE_ARGS },
  { "*", cm_asterisk, NO_BRACE_ARGS },
  { ",", cm_accent, BRACE_ARGS },
  { "-", cm_no_op, NO_BRACE_ARGS },
  { ".", insert_self, NO_BRACE_ARGS },
  { ":", cm_no_op, NO_BRACE_ARGS },
  { "=", insert_self, NO_BRACE_ARGS },
  { "?", insert_self, NO_BRACE_ARGS },
  { "@", insert_self, NO_BRACE_ARGS },
  { "^", insert_self, NO_BRACE_ARGS },
  { "`", insert_self, NO_BRACE_ARGS },
  { "{", insert_self, NO_BRACE_ARGS },
  { "|", cm_no_op, NO_BRACE_ARGS },
  { "}", insert_self, NO_BRACE_ARGS },
  { "~", insert_self, NO_BRACE_ARGS },
  { "AA", insert_self, BRACE_ARGS },
  { "AE", insert_self, BRACE_ARGS },
  { "H", cm_accent, BRACE_ARGS },
  { "L", cm_special_char, BRACE_ARGS },
  { "O", cm_special_char, BRACE_ARGS },
  { "OE", insert_self, BRACE_ARGS },
  { "TeX", cm_TeX, BRACE_ARGS },
  { "aa", insert_self, BRACE_ARGS },
  { "ae", insert_self, BRACE_ARGS },
  { "appendix", cm_appendix, NO_BRACE_ARGS },
  { "appendixsection", cm_appendixsec, NO_BRACE_ARGS },
  { "appendixsec", cm_appendixsec, NO_BRACE_ARGS },
  { "appendixsubsec", cm_appendixsubsec, NO_BRACE_ARGS },
  { "appendixsubsubsec", cm_appendixsubsubsec, NO_BRACE_ARGS },
  { "asis", cm_no_op, BRACE_ARGS },
  { "b", cm_not_fixed_width, BRACE_ARGS },
  { "bullet", cm_bullet, BRACE_ARGS },
  { "bye", cm_bye, NO_BRACE_ARGS },
  { "c", cm_ignore_line, NO_BRACE_ARGS },
  { "cartouche", cm_cartouche, NO_BRACE_ARGS },
  { "center", cm_center, NO_BRACE_ARGS },
  { "centerchap", cm_unnumbered, NO_BRACE_ARGS },
  { "chapheading", cm_chapheading, NO_BRACE_ARGS },
  { "chapter", cm_chapter, NO_BRACE_ARGS },
  { "cindex", cm_cindex, NO_BRACE_ARGS },
  { "cite", cm_cite, BRACE_ARGS },
  { "clear", cm_clear, NO_BRACE_ARGS },
  { "code", cm_code, BRACE_ARGS },
  { "comment", cm_ignore_line, NO_BRACE_ARGS },
  { "contents", cm_no_op, NO_BRACE_ARGS },
  { "copyright", cm_copyright, BRACE_ARGS },
  { "ctrl", cm_obsolete, BRACE_ARGS },
  { "defcodeindex", cm_defcodeindex, NO_BRACE_ARGS },
  { "defindex", cm_defindex, NO_BRACE_ARGS },
/* The `def' commands. */
  { "defcv", cm_defun, NO_BRACE_ARGS },
  { "defcvx", cm_defun, NO_BRACE_ARGS },
  { "deffn", cm_defun, NO_BRACE_ARGS },
  { "deffnx", cm_defun, NO_BRACE_ARGS },
  { "defivar", cm_defun, NO_BRACE_ARGS },
  { "defivarx", cm_defun, NO_BRACE_ARGS },
  { "defmac", cm_defun, NO_BRACE_ARGS },
  { "defmacx", cm_defun, NO_BRACE_ARGS },
  { "defmethod", cm_defun, NO_BRACE_ARGS },
  { "defmethodx", cm_defun, NO_BRACE_ARGS },
  { "defop", cm_defun, NO_BRACE_ARGS },
  { "defopt", cm_defun, NO_BRACE_ARGS },
  { "defoptx", cm_defun, NO_BRACE_ARGS },
  { "defopx", cm_defun, NO_BRACE_ARGS },
  { "defspec", cm_defun, NO_BRACE_ARGS },
  { "defspecx", cm_defun, NO_BRACE_ARGS },
  { "deftp", cm_defun, NO_BRACE_ARGS },
  { "deftpx", cm_defun, NO_BRACE_ARGS },
  { "deftypefn", cm_defun, NO_BRACE_ARGS },
  { "deftypefnx", cm_defun, NO_BRACE_ARGS },
  { "deftypefun", cm_defun, NO_BRACE_ARGS },
  { "deftypefunx", cm_defun, NO_BRACE_ARGS },
  { "deftypemethod", cm_defun, NO_BRACE_ARGS },
  { "deftypemethodx", cm_defun, NO_BRACE_ARGS },
  { "deftypevar", cm_defun, NO_BRACE_ARGS },
  { "deftypevarx", cm_defun, NO_BRACE_ARGS },
  { "deftypevr", cm_defun, NO_BRACE_ARGS },
  { "deftypevrx", cm_defun, NO_BRACE_ARGS },
  { "defun", cm_defun, NO_BRACE_ARGS },
  { "defunx", cm_defun, NO_BRACE_ARGS },
  { "defvar", cm_defun, NO_BRACE_ARGS },
  { "defvarx", cm_defun, NO_BRACE_ARGS },
  { "defvr", cm_defun, NO_BRACE_ARGS },
  { "defvrx", cm_defun, NO_BRACE_ARGS },
/* The end of the `def' commands. */
  { "detailmenu", cm_detailmenu, NO_BRACE_ARGS },
  { "dfn", cm_dfn, BRACE_ARGS },
  { "dircategory", cm_dircategory, NO_BRACE_ARGS },
  { "direntry", cm_direntry, NO_BRACE_ARGS },
  { "display", cm_display, NO_BRACE_ARGS },
  { "dmn", cm_no_op, BRACE_ARGS },
  { "dotaccent", cm_accent, BRACE_ARGS },
  { "dotless", cm_dotless, BRACE_ARGS },
  { "dots", cm_dots, BRACE_ARGS },
  { "email", cm_email, BRACE_ARGS },
  { "emph", cm_emph, BRACE_ARGS },
  { "end", cm_end, NO_BRACE_ARGS },
  { "enddots", cm_enddots, BRACE_ARGS },
  { "enumerate", cm_enumerate, NO_BRACE_ARGS },
  { "equiv", cm_equiv, BRACE_ARGS },
  { "error", cm_error, BRACE_ARGS },
  { "example", cm_example, NO_BRACE_ARGS },
  { "exclamdown", cm_special_char, BRACE_ARGS },
  { "exdent", cm_exdent, NO_BRACE_ARGS },
  { "expansion", cm_expansion, BRACE_ARGS },
  { "file", cm_code, BRACE_ARGS },
  { "finalout", cm_no_op, NO_BRACE_ARGS },
  { "findex", cm_findex, NO_BRACE_ARGS },
  { "flushleft", cm_flushleft, NO_BRACE_ARGS },
  { "flushright", cm_flushright, NO_BRACE_ARGS },
  { "footnote", cm_footnote, NO_BRACE_ARGS}, /* self-arg eater */
  { "footnotestyle", cm_footnotestyle, NO_BRACE_ARGS },
  { "format", cm_format, NO_BRACE_ARGS },
  { "ftable", cm_ftable, NO_BRACE_ARGS },
  { "group", cm_group, NO_BRACE_ARGS },
  { "heading", cm_heading, NO_BRACE_ARGS },
  { "headings", cm_ignore_line, NO_BRACE_ARGS },
  { "html", command_name_condition, NO_BRACE_ARGS },
  { "hyphenation", cm_no_op, BRACE_ARGS },
  { "i", cm_not_fixed_width, BRACE_ARGS },
  { "ifclear", cm_ifclear, NO_BRACE_ARGS },
  { "ifeq", cm_ifeq, NO_BRACE_ARGS },
  { "ifhtml", command_name_condition, NO_BRACE_ARGS },
  { "ifinfo", cm_ifinfo, NO_BRACE_ARGS },
  { "ifnothtml", cm_ifnothtml, NO_BRACE_ARGS },
  { "ifnotinfo", command_name_condition, NO_BRACE_ARGS },
  { "ifnottex", cm_ifnottex, NO_BRACE_ARGS },
  { "ifset", cm_ifset, NO_BRACE_ARGS },
  { "iftex", command_name_condition, NO_BRACE_ARGS },
  { "ignore", command_name_condition, NO_BRACE_ARGS },
  { "image", cm_image, BRACE_ARGS },
  { "include", cm_include, NO_BRACE_ARGS },
  { "inforef", cm_inforef, BRACE_ARGS },
  { "item", cm_item, NO_BRACE_ARGS },
  { "itemize", cm_itemize, NO_BRACE_ARGS },
  { "itemx", cm_itemx, NO_BRACE_ARGS },
  { "kbd", cm_kbd, BRACE_ARGS },
  { "kbdinputstyle", cm_no_op_line_arg, NO_BRACE_ARGS },
  { "key", cm_key, BRACE_ARGS },
  { "kindex", cm_kindex, NO_BRACE_ARGS },
  { "l", cm_special_char, BRACE_ARGS },
  { "lisp", cm_lisp, NO_BRACE_ARGS },
  { "lowersections", cm_lowersections, NO_BRACE_ARGS },
  { "macro", cm_macro, NO_BRACE_ARGS },
  { "majorheading", cm_majorheading, NO_BRACE_ARGS },
  { "math", cm_no_op, BRACE_ARGS },
  { "menu", cm_menu, NO_BRACE_ARGS },
  { "minus", cm_minus, BRACE_ARGS },
  { "multitable", cm_multitable, NO_BRACE_ARGS },
  { "need", cm_ignore_line, NO_BRACE_ARGS },
  { "node", cm_node, NO_BRACE_ARGS },
  { "noindent", cm_noindent, NO_BRACE_ARGS },
  { "nwnode", cm_node, NO_BRACE_ARGS },
  { "o", cm_special_char, BRACE_ARGS },
  { "oe", insert_self, BRACE_ARGS },
  { "page", cm_no_op, NO_BRACE_ARGS },
  { "paragraphindent", cm_paragraphindent, NO_BRACE_ARGS },
  { "pindex", cm_pindex, NO_BRACE_ARGS },
  { "point", cm_point, BRACE_ARGS },
  { "pounds", cm_special_char, BRACE_ARGS },
  { "print", cm_print, BRACE_ARGS },
  { "printindex", cm_printindex, NO_BRACE_ARGS },
  { "pxref", cm_pxref, BRACE_ARGS },
  { "questiondown", cm_special_char, BRACE_ARGS },
  { "quotation", cm_quotation, NO_BRACE_ARGS },
  { "r", cm_not_fixed_width, BRACE_ARGS },
  { "raisesections", cm_raisesections, NO_BRACE_ARGS },
  { "ref", cm_xref, BRACE_ARGS },
  { "refill", cm_no_op, NO_BRACE_ARGS },
  { "result", cm_result, BRACE_ARGS },
  { "ringaccent", cm_accent, BRACE_ARGS },
  { "samp", cm_code, BRACE_ARGS },
  { "sc", cm_var_sc, BRACE_ARGS },
  { "section", cm_section, NO_BRACE_ARGS },
  { "set", cm_set, NO_BRACE_ARGS },
  { "setchapternewpage", cm_ignore_line, NO_BRACE_ARGS },
  { "setchapterstyle", cm_obsolete, NO_BRACE_ARGS },
  { "setfilename", cm_setfilename, NO_BRACE_ARGS },
  { "settitle", cm_ignore_line, NO_BRACE_ARGS },
  { "shortcontents", cm_no_op, NO_BRACE_ARGS },
  { "shorttitlepage", cm_ignore_line, NO_BRACE_ARGS },
  { "smallbook", cm_ignore_line, NO_BRACE_ARGS },
  { "smallexample", cm_smallexample, NO_BRACE_ARGS },
  { "smalllisp", cm_smalllisp, NO_BRACE_ARGS },
  { "sp", cm_sp, NO_BRACE_ARGS },
  { "ss", insert_self, BRACE_ARGS },
  { "strong", cm_strong, BRACE_ARGS },
  { "subheading", cm_subheading, NO_BRACE_ARGS },
  { "subsection", cm_subsection, NO_BRACE_ARGS },
  { "subsubheading", cm_subsubheading, NO_BRACE_ARGS },
  { "subsubsection", cm_subsubsection, NO_BRACE_ARGS },
  { "summarycontents", cm_no_op, NO_BRACE_ARGS },
  { "syncodeindex", cm_synindex, NO_BRACE_ARGS },
  { "synindex", cm_synindex, NO_BRACE_ARGS },
  { "t", cm_no_op, BRACE_ARGS },
  { "tab", cm_tab, NO_BRACE_ARGS },
  { "table", cm_table, NO_BRACE_ARGS },
  { "tex", command_name_condition, NO_BRACE_ARGS },
  { "tieaccent", cm_accent, BRACE_ARGS },
  { "tindex", cm_tindex, NO_BRACE_ARGS },
  { "titlefont", cm_not_fixed_width, BRACE_ARGS },
  { "titlepage", command_name_condition, NO_BRACE_ARGS },
  { "today", cm_today, BRACE_ARGS },
  { "top", cm_top, NO_BRACE_ARGS  },
  { "u", cm_accent, BRACE_ARGS },
  { "ubaraccent", cm_accent, BRACE_ARGS },
  { "udotaccent", cm_accent, BRACE_ARGS },
#if defined (HAVE_MACROS)
  { "unmacro", cm_unmacro, NO_BRACE_ARGS },
#endif
  { "unnumbered", cm_unnumbered, NO_BRACE_ARGS },
  { "unnumberedsec", cm_unnumberedsec, NO_BRACE_ARGS },
  { "unnumberedsubsec", cm_unnumberedsubsec, NO_BRACE_ARGS },
  { "unnumberedsubsubsec", cm_unnumberedsubsubsec, NO_BRACE_ARGS },
  { "uref", cm_uref, BRACE_ARGS },
  { "url", cm_code, BRACE_ARGS },
  { "v", cm_accent, BRACE_ARGS },
  { "value", cm_value, BRACE_ARGS },
  { "var", cm_var_sc, BRACE_ARGS },
  { "vindex", cm_vindex, NO_BRACE_ARGS },
  { "vtable", cm_vtable, NO_BRACE_ARGS },
  { "w", cm_w, BRACE_ARGS },
  { "xref", cm_xref, BRACE_ARGS },

  /* Deprecated commands.  These used to be for italics.  */
  { "iappendix", cm_ideprecated, NO_BRACE_ARGS },
  { "iappendixsec", cm_ideprecated, NO_BRACE_ARGS },
  { "iappendixsection", cm_ideprecated, NO_BRACE_ARGS },
  { "iappendixsubsec", cm_ideprecated, NO_BRACE_ARGS },
  { "iappendixsubsubsec", cm_ideprecated, NO_BRACE_ARGS },
  { "ichapter", cm_ideprecated, NO_BRACE_ARGS },
  { "isection", cm_ideprecated, NO_BRACE_ARGS },
  { "isubsection", cm_ideprecated, NO_BRACE_ARGS },
  { "isubsubsection", cm_ideprecated, NO_BRACE_ARGS },
  { "iunnumbered", cm_ideprecated, NO_BRACE_ARGS },
  { "iunnumberedsec", cm_ideprecated, NO_BRACE_ARGS },
  { "iunnumberedsubsec", cm_ideprecated, NO_BRACE_ARGS },
  { "iunnumberedsubsubsec", cm_ideprecated, NO_BRACE_ARGS },

  /* Now @include does what this was used to. */
  { "infoinclude", cm_obsolete, NO_BRACE_ARGS },
  { "titlespec", cm_obsolete, NO_BRACE_ARGS },

  { NULL, NULL, NO_BRACE_ARGS }
};

struct option long_options[] =
{
  { "error-limit", 1, 0, 'e' },                 /* formerly -el */
  { "fill-column", 1, 0, 'f' },                 /* formerly -fc */
  { "footnote-style", 1, 0, 's' },              /* formerly -ft */
  { "force", 0, 0, 'F' },                       /* do not remove output */
  { "no-headers", 0, &no_headers, 1 },          /* do not output Node: foo */
  { "no-pointer-validate", 0, &validating, 0 }, /* formerly -nv */
  { "no-validate", 0, &validating, 0 },         /* formerly -nv */
  { "no-split", 0, &splitting, 0 },             /* formerly -ns */
  { "no-warn", 0, &print_warnings, 0 },         /* formerly -nw */
  { "macro-expand", 1, 0, 'E' },
  { "number-footnotes", 0, &number_footnotes, 1 },
  { "no-number-footnotes", 0, &number_footnotes, 0 },
  { "output", 1, 0, 'o' },
  { "paragraph-indent", 1, 0, 'p' },            /* formerly -pi */
  { "reference-limit", 1, 0, 'r' },             /* formerly -rl */
  { "verbose", 0, &verbose_mode, 1 },           /* formerly -verbose */
  { "help", 0, 0, 'h' },
  { "version", 0, 0, 'V' },
  {NULL, 0, NULL, 0}
};

/* **************************************************************** */
/*                                                                  */
/*                      Error Handling                              */
/*                                                                  */
/* **************************************************************** */

/* Number of errors encountered. */
int errors_printed = 0;

/* Print the last error gotten from the file system. */
int
fs_error (filename)
     char *filename;
{
  remember_error ();
  perror (filename);
  return (0);
}

/* Print an error message, and return false. */
void
#if defined (VA_FPRINTF) && __STDC__
error (char *format, ...)
#else
error (format, va_alist)
     char *format;
     va_dcl
#endif
{
#ifdef VA_FPRINTF
  va_list ap;
#endif

  remember_error ();

  VA_START (ap, format);
#ifdef VA_FPRINTF
  VA_FPRINTF (stderr, format, ap);
#else
  fprintf (stderr, format, a1, a2, a3, a4, a5, a6, a7, a8);
#endif /* not VA_FPRINTF */
  va_end (ap);

  putc ('\n', stderr);
}

/* Just like error (), but print the line number as well. */
void
#if defined (VA_FPRINTF) && __STDC__
line_error (char *format, ...)
#else
line_error (format, va_alist)
   char *format;
   va_dcl
#endif
{
#ifdef VA_FPRINTF
  va_list ap;
#endif

  remember_error ();
  fprintf (stderr, "%s:%d: ", input_filename, line_number);

  VA_START (ap, format);
#ifdef VA_FPRINTF
  VA_FPRINTF (stderr, format, ap);
#else
  fprintf (stderr, format, a1, a2, a3, a4, a5, a6, a7, a8);
#endif /* not VA_FPRINTF */
  va_end (ap);

  fprintf (stderr, ".\n");
}

void
#if defined (VA_FPRINTF) && __STDC__
warning (char *format, ...)
#else
warning (format, va_alist)
     char *format;
     va_dcl
#endif
{
#ifdef VA_FPRINTF
  va_list ap;
#endif

  if (print_warnings)
    {
      fprintf (stderr, _("%s:%d: warning: "), input_filename, line_number);

      VA_START (ap, format);
#ifdef VA_FPRINTF
      VA_FPRINTF (stderr, format, ap);
#else
      fprintf (stderr, format, a1, a2, a3, a4, a5, a6, a7, a8);
#endif /* not VA_FPRINTF */
      va_end (ap);

      fprintf (stderr, ".\n");
    }
}


/* Remember that an error has been printed.  If more than
   max_error_level have been printed, then exit the program. */
void
remember_error ()
{
  errors_printed++;
  if (max_error_level && (errors_printed > max_error_level))
    {
      fprintf (stderr, _("Too many errors!  Gave up.\n"));
      flush_file_stack ();
      cm_bye ();
      exit (FATAL);
    }
}

/* **************************************************************** */
/*                                                                  */
/*                      Main ()  Start of code                      */
/*                                                                  */
/* **************************************************************** */

/* For each file mentioned in the command line, process it, turning
   Texinfo commands into wonderfully formatted output text. */
int
main (argc, argv)
     int argc;
     char **argv;
{
  extern int errors_printed;
  char *filename_part ();
  int c, ind;
  int reading_from_stdin = 0;

  /* The name of this program is the last filename in argv[0]. */
  progname = filename_part (argv[0]);

#ifdef HAVE_SETLOCALE
  /* Do not use LC_ALL, because LC_NUMERIC screws up the scanf parsing
     of the argument to @multicolumn.  */
  setlocale (LC_TIME, "");
  setlocale (LC_MESSAGES, "");
#endif

  /* Set the text message domain.  */
  bindtextdomain (PACKAGE, LOCALEDIR);
  textdomain (PACKAGE);

  /* Parse argument flags from the input line. */
  while ((c = getopt_long (argc, argv, "D:e:E:f:I:o:p:P:r:s:U:V",
                           long_options, &ind)) != EOF)
    {
      if (c == 0 && long_options[ind].flag == 0)
        c = long_options[ind].val;

      switch (c)
        {
        case 'D':
        case 'U':
          /* User specified variable to set or clear. */
          handle_variable_internal ((c == 'D') ? SET : CLEAR, optarg);
          break;

        case 'e':
          /* User specified error level. */
          if (sscanf (optarg, "%d", &max_error_level) != 1)
            {
              fprintf (stderr,
                      _("%s: %s arg must be numeric, not `%s'.\n"),
                      "--error-limit", progname, optarg);
              usage (stderr, FATAL);
            }
          break;

        case 'E':
          /* User specified a macro expansion output file. */
          if (!macro_expansion_output_stream)
            {
              macro_expansion_filename = optarg;
              macro_expansion_output_stream
                = strcmp (optarg, "-") == 0 ? stdout : fopen (optarg, "w");
              if (!macro_expansion_output_stream)
                error (_("Couldn't open macro expansion output `%s'"), optarg);
            }
          else
            error (_("Cannot specify more than one macro expansion output"));
          break;

        case 'f':
          /* User specified fill_column. */
          if (sscanf (optarg, "%d", &fill_column) != 1)
            {
              fprintf (stderr,
                       _("%s: %s arg must be numeric, not `%s'.\n"), 
                       "--fill-column", progname, optarg);
              usage (FATAL);
            }
          break;

        case 'F':
          force++; /* Do not remove erroneous output.  */
          break;
          
        case 'h':
          usage (NO_ERROR);
          break;

        case 'I':
          /* Append user-specified dir to include file path. */
          if (!include_files_path)
            include_files_path = xstrdup (".");

          include_files_path = (char *)
            xrealloc (include_files_path,
                      2 + strlen (include_files_path) + strlen (optarg));
          strcat (include_files_path, ":");
          strcat (include_files_path, optarg);
          break;

        case 'o':
          /* User specified output file. */
          command_output_filename = xstrdup (optarg);
          break;

        case 'p':
          /* User specified paragraph indent (paragraph_start_index). */
          if (set_paragraph_indent (optarg) < 0)
            {
              fprintf (stderr,
   _("%s: --paragraph-indent arg must be numeric/`none'/`asis', not `%s'.\n"), 
                       progname, optarg);
              usage (FATAL);
            }
          break;

        case 'P':
          /* Prepend user-specified include dir to include path. */
          if (!include_files_path)
            {
              include_files_path = xstrdup (optarg);
              include_files_path = (char *) xrealloc (include_files_path,
                           strlen (include_files_path) + 3); /* 3 for ":.\0" */
              strcat (include_files_path, ":.");
            }
          else
            {
              char *tmp = xstrdup (include_files_path);
              include_files_path = (char *) xrealloc (include_files_path,
          strlen (include_files_path) + strlen (optarg) + 2); /* 2 for ":\0" */
              strcpy (include_files_path, optarg);
              strcat (include_files_path, ":");
              strcat (include_files_path, tmp);
              free (tmp);
            }
          break;
 
        case 'r':
          /* User specified reference warning limit. */
          if (sscanf (optarg, "%d", &reference_warning_limit) != 1)
            {
              fprintf (stderr,
                     _("%s: %s arg must be numeric, not `%s'.\n"), 
                     "--reference-limit", progname, optarg);
              usage (FATAL);
            }
          break;

        case 's':
          /* User specified footnote style. */
          if (set_footnote_style (optarg) < 0)
            {
              fprintf (stderr,
          _("%s: --footnote-style arg must be `separate' or `end', not `%s'.\n"), 
                       progname, optarg);
              usage (FATAL);
            }
          footnote_style_preset = 1;
          break;

        case 'V':
          /* User requested version info. */
          print_version_info ();
	  printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
There is NO warranty.  You may redistribute this software\n\
under the terms of the GNU General Public License.\n\
For more information about these matters, see the files named COPYING.\n"),
		  "1998");
          exit (NO_ERROR);
          break;

        case '?':
          usage (FATAL);
          break;
        }
    }

  if (optind == argc)
    {
      /* Check to see if input is a file.  If so, process that. */
      if (!isatty (fileno (stdin)))
        reading_from_stdin = 1;
      else
        {
          fprintf (stderr, _("%s: missing file argument.\n"), progname);
          usage (FATAL);
        }
    }

  /* If the user has specified --no-headers, this should imply --no-split.
     Do that here.  I think it might also imply that we should ignore the
     setfilename at the top of the file, but this might break some FSF things,
     so I will hold off on that. */
  if (no_headers)
    {
      splitting = 0;

      /* If the user has not specified an output file, use stdout. */
      if (!command_output_filename)
        command_output_filename = xstrdup ("-");
    }

  if (verbose_mode)
    print_version_info ();

  /* Remaining arguments are file names of texinfo files.
     Convert them, one by one. */
  if (!reading_from_stdin)
    {
      while (optind != argc)
        convert_from_file (argv[optind++]);
    }
  else
    convert_from_stream (stdin, "stdin");

  if (errors_printed)
    return (SYNTAX);
  else
    return (NO_ERROR);
}

/* Display the version info of this invocation of Makeinfo. */
void
print_version_info ()
{
  printf ("makeinfo (GNU %s %s) %d.%d\n", PACKAGE, VERSION,
          major_version, minor_version);
}

/* If EXIT_VALUE is zero, print the full usage message to stdout.
   Otherwise, just say to use --help for more info.
   Then exit with EXIT_VALUE. */
void
usage (exit_value)
     int exit_value;
{
  if (exit_value != 0)
    fprintf (stderr, _("Try `%s --help' for more information.\n"), progname);
  else
    printf (_("Usage: %s [OPTION]... TEXINFO-FILE...\n\
\n\
Translate Texinfo source documentation to a format suitable for reading\n\
with GNU Info.\n\
\n\
Options:\n\
-D VAR                 define a variable, as with @set.\n\
-E MACRO-OFILE         process macros only, output texinfo source.\n\
-I DIR                 append DIR to the @include directory search path.\n\
-P DIR                 prepend DIR to the @include directory search path.\n\
-U VAR                 undefine a variable, as with @clear.\n\
--error-limit NUM      quit after NUM errors (default %d).\n\
--fill-column NUM      break lines at NUM characters (default %d).\n\
--footnote-style STYLE output footnotes according to STYLE:\n\
                         `separate' to place footnotes in their own node,\n\
                         `end' to place the footnotes at the end of\n\
                         the node in which they are defined (the default).\n\
--force                preserve output even if errors.\n\
--help                 display this help and exit.\n\
--no-validate          suppress node cross-reference validation.\n\
--no-warn              suppress warnings (but not errors).\n\
--no-split             suppress splitting of large files.\n\
--no-headers           suppress node separators and Node: Foo headers.\n\
--output FILE, -o FILE output to FILE, and ignore any @setfilename.\n\
--paragraph-indent VAL indent paragraphs with VAL spaces (default %d).\n\
                         if VAL is `none', do not indent; if VAL is `asis',\n\
                         preserve any existing indentation.\n\
--reference-limit NUM  complain about at most NUM references (default %d).\n\
--verbose              report about what is being done.\n\
--version              display version information and exit.\n\
\n\
Email bug reports to bug-texinfo@gnu.org.\n\
"),
           progname, max_error_level, fill_column,
           paragraph_start_indent, reference_warning_limit);
  exit (exit_value);
}

/* Manipulating Lists */

typedef struct generic_list {
  struct generic_list *next;
} GENERIC_LIST;

/* Reverse the chain of structures in LIST.  Output the new head
   of the chain.  You should always assign the output value of this
   function to something, or you will lose the chain. */
GENERIC_LIST *
reverse_list (list)
     register GENERIC_LIST *list;
{
  register GENERIC_LIST *next;
  register GENERIC_LIST *prev = (GENERIC_LIST *) NULL;

  while (list)
    {
      next = list->next;
      list->next = prev;
      prev = list;
      list = next;
    }
  return (prev);
}

/* Pushing and Popping Files */

/* Find and load the file named FILENAME.  Return a pointer to
   the loaded file, or NULL if it can't be loaded. */
char *
find_and_load (filename)
     char *filename;
{
  struct stat fileinfo;
  long file_size;
  int file = -1, count = 0;
  char *fullpath, *result, *get_file_info_in_path ();

  result = fullpath = (char *)NULL;

  fullpath = get_file_info_in_path (filename, include_files_path, &fileinfo);

  if (!fullpath)
    goto error_exit;

  filename = fullpath;
  file_size = (long) fileinfo.st_size;

  file = open (filename, O_RDONLY);
  if (file < 0)
    goto error_exit;

  /* Load the file, with enough room for a newline and a null. */
  result = xmalloc (file_size + 2);

  /* VMS stat lies about the st_size value.  The actual number of
     readable bytes is always less than this value.  The arcane
     mysteries of VMS/RMS are too much to probe, so this hack
    suffices to make things work. */
#if defined (VMS) || defined (WIN32)
#ifdef VMS
  while ((n = read (file, result + count, file_size)) > 0)
#else /* WIN32 */
  while ((n = read (file, result + count, 1)) > 0)
#endif /* WIN32 */
    count += n;
  if (n == -1)
#else /* !VMS && !WIN32 */
    count = file_size;
    if (read (file, result, file_size) != file_size)
#endif /* !VMS && !WIN32 */
  error_exit:
    {
      if (result)
        free (result);

      if (fullpath)
        free (fullpath);

      if (file != -1)
        close (file);

      return ((char *) NULL);
    }
  close (file);

  /* Set the globals to the new file. */
  input_text = result;
  size_of_input_text = count;
  input_filename = fullpath;
  node_filename = xstrdup (fullpath);
  input_text_offset = 0;
  line_number = 1;
  /* Not strictly necessary.  This magic prevents read_token () from doing
     extra unnecessary work each time it is called (that is a lot of times).
     SIZE_OF_INPUT_TEXT is one past the actual end of the text. */
  input_text[size_of_input_text] = '\n';
  /* This, on the other hand, is always necessary.  */
  input_text[size_of_input_text+1] = 0;
  return (result);
}

/* Save the state of the current input file. */
void
pushfile ()
{
  FSTACK *newstack = (FSTACK *) xmalloc (sizeof (FSTACK));
  newstack->filename = input_filename;
  newstack->text = input_text;
  newstack->size = size_of_input_text;
  newstack->offset = input_text_offset;
  newstack->line_number = line_number;
  newstack->next = filestack;

  filestack = newstack;
  push_node_filename ();
}

/* Make the current file globals be what is on top of the file stack. */
void
popfile ()
{
  FSTACK *tos = filestack;

  if (!tos)
    abort ();                   /* My fault.  I wonder what I did? */

#if defined (HAVE_MACROS)
  if (macro_expansion_output_stream)
    {
      maybe_write_itext (input_text, input_text_offset);
      forget_itext (input_text);
    }
#endif /* HAVE_MACROS */

  /* Pop the stack. */
  filestack = filestack->next;

  /* Make sure that commands with braces have been satisfied. */
  if (!executing_string && !me_executing_string)
    discard_braces ();

  /* Get the top of the stack into the globals. */
  input_filename = tos->filename;
  input_text = tos->text;
  size_of_input_text = tos->size;
  input_text_offset = tos->offset;
  line_number = tos->line_number;
  free (tos);

  /* Go back to the (now) current node. */
  pop_node_filename ();
}

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

int node_filename_stack_index = 0;
int node_filename_stack_size = 0;
char **node_filename_stack = (char **)NULL;

void
push_node_filename ()
{
  if (node_filename_stack_index + 1 > node_filename_stack_size)
    node_filename_stack = (char **)xrealloc
    (node_filename_stack, (node_filename_stack_size += 10) * sizeof (char *));

  node_filename_stack[node_filename_stack_index] = node_filename;
  node_filename_stack_index++;
}

void
pop_node_filename ()
{
  node_filename = node_filename_stack[--node_filename_stack_index];
}

/* Return just the simple part of the filename; i.e. the
   filename without the path information, or extensions.
   This conses up a new string. */
char *
filename_part (filename)
     char *filename;
{
  char *basename;

  basename = strrchr (filename, '/');
  if (!basename)
    basename = filename;
  else
    basename++;

  basename = xstrdup (basename);
#if defined (REMOVE_OUTPUT_EXTENSIONS)

  /* See if there is an extension to remove.  If so, remove it. */
  {
    char *temp;

    temp = strrchr (basename, '.');
    if (temp)
      *temp = 0;
  }
#endif /* REMOVE_OUTPUT_EXTENSIONS */
  return (basename);
}

/* 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 = (char *)xmalloc (1 + i);
      strncpy (result, filename, i);
      result[i] = 0;
    }
  free (filename);
  return (result);
}

char *
filename_non_directory (name)
     char *name;
{
  register int i;

  for (i = strlen (name) - 1; i; i--)
    if (name[i] == '/')
      return (xstrdup (name + i + 1));

  return (xstrdup (name));
}

/* Return the expansion of FILENAME. */
char *
expand_filename (filename, input_name)
     char *filename, *input_name;
{
  register int i;
  char *full_pathname ();

  if (filename)
    filename = full_pathname (filename);
  else
    {
      filename = filename_non_directory (input_name);

      if (!*filename)
        {
          free (filename);
          filename = xstrdup ("noname.texi");
        }

      for (i = strlen (filename) - 1; i; i--)
        if (filename[i] == '.')
          break;

      if (!i)
        i = strlen (filename);

      if (i + 6 > (strlen (filename)))
        filename = (char *)xrealloc (filename, i + 6);
      strcpy (filename + i, ".info");
      return (filename);
    }
        
  if (filename[0] == '.' || filename[0] == '/')
    return (filename);

  if (filename[0] != '/' && input_name[0] == '/')
    {
      /* Make it so that relative names work. */
      char *result;
      
      i = strlen (input_name) - 1;

      result = (char *)xmalloc (1 + strlen (input_name) + strlen (filename));
      strcpy (result, input_name);

      while (result[i] != '/' && i)
        i--;

      if (result[i] == '/')
        i++;

      strcpy (&result[i], filename);
      free (filename);
      return (result);
    }
  return (filename);
}

/* Return the full path to FILENAME. */
char *
full_pathname (filename)
     char *filename;
{
  int initial_character;
  char *result;

  /* No filename given? */
  if (!filename || !(initial_character = *filename))
    return (xstrdup (""));
  
  /* Already absolute? */
  if ((initial_character == '/') ||
      ((strncmp (filename, "./", 2) == 0) ||
       (strncmp (filename, "../", 3) == 0)))
    return (xstrdup (filename));

  if (initial_character != '~')
    {
      char *localdir;

      localdir = (char *)xmalloc (1025);
#if defined (HAVE_GETCWD)
      if (!getcwd (localdir, 1024))
#else  /*  !HAVE_GETCWD */
        if (!getwd (localdir))
#endif /* !HAVE_GETCWD */
          {
            fprintf (stderr, _("%s: getwd: %s, %s\n"),
                     progname, filename, localdir);
            exit (1);
          }

      strcat (localdir, "/");
      strcat (localdir, filename);
      result = xstrdup (localdir);
      free (localdir);
    }
  else
    {
#ifndef WIN32
      if (filename[1] == '/')
        {
          /* Return the concatenation of the environment variable HOME
             and the rest of the string. */
          char *temp_home;

          temp_home = (char *) getenv ("HOME");
          result = (char *)xmalloc (strlen (&filename[1])
                                    + 1
                                    + temp_home ? strlen (temp_home)
                                    : 0);
          *result = 0;

          if (temp_home)
            strcpy (result, temp_home);

          strcat (result, &filename[1]);
        }
      else
        {
          struct passwd *user_entry;
          int i, c;
          char *username = (char *)xmalloc (257);

          for (i = 1; (c = filename[i]); i++)
            {
              if (c == '/')
                break;
              else
                username[i - 1] = c;
            }
          if (c)
            username[i - 1] = 0;

          user_entry = getpwnam (username);

          if (!user_entry)
            return (xstrdup (filename));

          result = (char *)xmalloc (1 + strlen (user_entry->pw_dir)
                                    + strlen (&filename[i]));
          strcpy (result, user_entry->pw_dir);
          strcat (result, &filename[i]);
        }
    }
#endif /* not WIN32 */
  return (result);
}

char *
output_name_from_input_name (name)
     char *name;
{
  return (expand_filename ((char *)NULL, name));
}

/* **************************************************************** */
/*                                                                  */
/*                      Hacking Tokens and Strings                  */
/*                                                                  */
/* **************************************************************** */

/* Return the next token as a string pointer.  We cons the string. */
char *
read_token ()
{
  int i, character;
  char *result;

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

      if (character == '\n')
        line_number++;

      result = xstrdup (" ");
      *result = character;
      return (result);
    }

  for (i = 0; ((input_text_offset != size_of_input_text)
               && (character = curchar ())
               && command_char (character));
       i++, input_text_offset++);
  result = (char *)xmalloc (i + 1);
  memcpy (result, &input_text[input_text_offset - i], i);
  result[i] = 0;
  return (result);
}

/* Return nonzero if CHARACTER is self-delimiting. */
int
self_delimiting (character)
     int character;
{
  /* @; and @\ are not Texinfo commands, but they are listed here
     anyway.  I don't know why.  --karl, 10aug96.  */
  return member (character, "~{|}`^\\@?=;:.-,*\'\" !\n\t");
}

/* Clear whitespace from the front and end of string. */
void
canon_white (string)
     char *string;
{
  int len = strlen (string);
  int x;

  if (!len)
    return;

  for (x = 0; x < len; x++)
    {
      if (!cr_or_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. */
void
fix_whitespace (string)
     char *string;
{
  char *temp = (char *)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. */
void
discard_until (string)
     char *string;
{
  int temp = search_forward (string, input_text_offset);

  int tt = (temp < 0) ? size_of_input_text : temp + strlen (string);
  int from = input_text_offset;

  /* Find out what line we are on. */
  while (from != tt)
    if (input_text[from++] == '\n')
      line_number++;

  if (temp < 0)
    {
      input_text_offset = size_of_input_text - strlen (string);

      if (strcmp (string, "\n") != 0)
        {
          line_error (_("Expected `%s'"), string);
          return;
        }
    }
  else
    input_text_offset = temp;

  input_text_offset += strlen (string);
}

/* Read characters from the file until we are at MATCH.
   Place the characters read into STRING.
   On exit input_text_offset is after the match string.
   Return the offset where the string starts. */
int
get_until (match, string)
     char *match, **string;
{
  int len, current_point, x, new_point, tem;

  current_point = x = input_text_offset;
  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. */
  tem = new_point + (strlen (match) - 1);
  while (x != tem)
    if (input_text[x++] == '\n')
      line_number++;

  *string = (char *)xmalloc (len + 1);

  memcpy (*string, &input_text[current_point], len);
  (*string)[len] = 0;

  /* Now leave input_text_offset in a consistent state. */
  input_text_offset = tem;

  if (input_text_offset > size_of_input_text)
    input_text_offset = size_of_input_text;

  return (new_point);
}

/* Read characters from the file until we are at MATCH or end of line.
   Place the characters read into STRING.  */
void
get_until_in_line (expand, match, string)
     int expand;
     char *match, **string;
{
  int real_bottom = size_of_input_text;
  int limit = search_forward ("\n", input_text_offset);
  if (limit < 0)
    limit = size_of_input_text;

  /* Replace input_text[input_text_offset .. limit-1] with its macro
     expansion (actually, we expand all commands).  This allows the node
     names themselves to be constructed via a macro, as in:
        @macro foo{p, q}
        Together: \p\ & \q\.
        @end macro

        @node @foo{A,B}, next, prev, top
     
     Otherwise, the `,' separating the macro args A and B is taken as
     the node argument separator, so the node name is `@foo{A'.  This
     expansion is only necessary on the first call, since we expand the
     whole line then.
     
     Furthermore, if we're executing a string, don't do it -- we'll end
     up shrinking the execution string which is currently aliased to
     `input_text', so it might get moved, and not updated in the
     `execution_strings' array.  This happens when processing the
     (synthetic) Overview-Footnotes node in the Texinfo manual.  */

  if (expand && !executing_string && !me_executing_string)
    {
      char *xp;
      unsigned xp_len, new_len;
      
      /* Get original string from input.  */
      unsigned raw_len = limit - input_text_offset;
      char *str = xmalloc (raw_len + 1);
      strncpy (str, input_text + input_text_offset, raw_len);
      str[raw_len] = 0;
      
      /* Expand it.  */
      xp = expansion (str, 0);
      xp_len = strlen (xp);
      free (str);
      
      /* Plunk the expansion into the middle of `input_text' --
         which is terminated by a newline, not a null.  */
      str = xmalloc (real_bottom - limit + 1);
      strncpy (str, input_text + limit, real_bottom - limit + 1);
      new_len = input_text_offset + xp_len + real_bottom - limit + 1;
      input_text = xrealloc (input_text, new_len);
      strcpy (input_text + input_text_offset, xp);
      strncpy (input_text + input_text_offset + xp_len, str,
	       real_bottom - limit + 1);
      free (str);
      free (xp);
      
      limit += xp_len - raw_len;
      real_bottom += xp_len - raw_len;
    }

  size_of_input_text = limit;
  get_until (match, string);
  size_of_input_text = real_bottom;
}

void
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++;
    }
}

/* Backup the input pointer to the previous character, keeping track
   of the current line number. */
void
backup_input_pointer ()
{
  if (input_text_offset)
    {
      input_text_offset--;
      if (curchar () == '\n')
        line_number--;
    }
}

/* Read characters from the file until we are at MATCH or closing brace.
   Place the characters read into STRING.  */
void
get_until_in_braces (match, string)
     char *match, **string;
{
  char *temp;
  int i, brace = 0;
  int match_len = strlen (match);

  for (i = input_text_offset; i < size_of_input_text; i++)
    {
      if (input_text[i] == '{')
        brace++;
      else if (input_text[i] == '}')
        brace--;
      else 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 = (char *)xmalloc (2 + match_len);
  strncpy (temp, input_text + input_text_offset, match_len);
  temp[match_len] = 0;
  input_text_offset = i;
  *string = temp;
}

/* **************************************************************** */
/*                                                                  */
/*                      Converting the File                         */
/*                                                                  */
/* **************************************************************** */

/* Convert the file named by NAME.  The output is saved on the file
   named as the argument to the @setfilename command. */
static char *suffixes[] = {
  ".texinfo",
  ".texi",
  ".txinfo",
  "",
  (char *)NULL
};

void
initialize_conversion ()
{
  init_tag_table ();
  init_indices ();
  init_internals ();
  init_paragraph ();

  /* This is used for splitting the output file and for doing section
     headings.  It was previously initialized in `init_paragraph', but its
     use there loses with the `init_paragraph' calls done by the
     multitable code; the tag indices get reset to zero.  */
  output_position = 0;
}

/* We read in multiples of 4k, simply because it is a typical pipe size
   on unix systems. */
#define READ_BUFFER_GROWTH (4 * 4096)

/* Convert the Texinfo file coming from the open stream STREAM.  Assume the
   source of the stream is named NAME. */
void
convert_from_stream (stream, name)
     FILE *stream;
     char *name;
{
  char *buffer = (char *)NULL;
  int buffer_offset = 0, buffer_size = 0;

  initialize_conversion ();

  /* Read until the end of the stream.  This isn't strictly correct, since
     the texinfo input may end before the stream ends, but it is a quick
     working hueristic. */
  while (!feof (stream))
    {
      int count;

      if (buffer_offset + (READ_BUFFER_GROWTH + 1) >= buffer_size)
        buffer = (char *)
          xrealloc (buffer, (buffer_size += READ_BUFFER_GROWTH));

      count = fread (buffer + buffer_offset, 1, READ_BUFFER_GROWTH, stream);

      if (count < 0)
        {
          perror (name);
          exit (FATAL);
        }

      buffer_offset += count;
      if (count == 0)
        break;
    }

  /* Set the globals to the new file. */
  input_text = buffer;
  size_of_input_text = buffer_offset;
  input_filename = xstrdup (name);
  node_filename = xstrdup (name);
  input_text_offset = 0;
  line_number = 1;

  /* Not strictly necessary.  This magic prevents read_token () from doing
     extra unnecessary work each time it is called (that is a lot of times).
     The SIZE_OF_INPUT_TEXT is one past the actual end of the text. */
  input_text[size_of_input_text] = '\n';

  convert_from_loaded_file (name);
}

void
convert_from_file (name)
     char *name;
{
  register int i;
  char *filename = (char *)xmalloc (strlen (name) + 50);

  initialize_conversion ();

  /* Try to load the file specified by NAME, concatenated with our
     various suffixes.  Prefer files like `makeinfo.texi' to
     `makeinfo'.  */
  for (i = 0; suffixes[i]; i++)
    {
      strcpy (filename, name);
      strcat (filename, suffixes[i]);

      if (find_and_load (filename))
        break;

      if (!suffixes[i][0] && strrchr (filename, '.'))
        {
          fs_error (filename);
          free (filename);
          return;
        }
    }

  if (!suffixes[i])
    {
      fs_error (name);
      free (filename);
      return;
    }

  input_filename = filename;

  convert_from_loaded_file (name);
}
  
void
convert_from_loaded_file (name)
     char *name;
{
  char *expand_filename (), *filename_part ();
  char *real_output_filename = (char *)NULL;

#if defined (HAVE_MACROS)
  remember_itext (input_text, 0);
#endif /* HAVE_MACROS */

  /* Search this file looking for the special string which starts conversion.
     Once found, we may truly begin. */
  input_text_offset = 0;
  while (input_text_offset >= 0)
    {
      input_text_offset =
        search_forward (setfilename_search, input_text_offset);

      if ((input_text_offset == 0) ||
          ((input_text_offset > 0) &&
           (input_text[input_text_offset -1] == '\n')))
        break;
      else if (input_text_offset > 0)
        input_text_offset++;
    }

  if (input_text_offset < 0)
    {
      if (!command_output_filename)
        {
#if defined (REQUIRE_SETFILENAME)
          error (_("No `%s' found in `%s'"), setfilename_search, name);
          goto finished;
#else
          register int i, end_of_first_line;

          /* Find the end of the first line in the file. */
          for (i = 0; i < size_of_input_text - 1; i++)
            if (input_text[i] == '\n')
              break;

          end_of_first_line = i + 1;

          input_text_offset = 0;

          for (i = 0; i < end_of_first_line; i++)
            {
              if ((input_text[i] == '\\') &&
                  (strncmp (input_text + i + 1, "include", 7) == 0))
                {
                  input_text_offset = end_of_first_line;
                  break;
                }
            }
          command_output_filename = output_name_from_input_name (name);
#endif /* !REQUIRE_SETFILENAME */
        }
    }
  else
    input_text_offset += strlen (setfilename_search);

  if (!command_output_filename)
    get_until ("\n", &output_filename);
  else
    {
      if (input_text_offset != -1)
        discard_until ("\n");
      else
        input_text_offset = 0;

      real_output_filename = output_filename = command_output_filename;
      command_output_filename = (char *)NULL;
    }

  canon_white (output_filename);

  if (real_output_filename && strcmp (real_output_filename, "-") == 0)
    {
      if (macro_expansion_filename
          && strcmp (macro_expansion_filename, "-") == 0)
        {
          fprintf (stderr, _("%s: Skipping macro expansion to stdout as Info output is going there.\n"),
                   progname);
          macro_expansion_output_stream = NULL;
        }
      real_output_filename = xstrdup (real_output_filename);
      output_stream = stdout;
      splitting = 0;            /* Cannot split when writing to stdout. */
    }
  else
    {
      if (!real_output_filename)
        real_output_filename = expand_filename (output_filename, name);
      else
        real_output_filename = xstrdup (real_output_filename);

      output_stream = fopen (real_output_filename, "w");
    }

  if (output_stream != stdout)
    printf (_("Making %s file `%s' from `%s'.\n"),
            no_headers ? "text" : "info", output_filename, input_filename);

  if (output_stream == NULL)
    {
      fs_error (real_output_filename);
      goto finished;
    }

  /* Make the displayable filename from output_filename.  Only the base
     portion of the filename need be displayed. */
  if (output_stream != stdout)
    pretty_output_filename = filename_part (output_filename);
  else
    pretty_output_filename = xstrdup ("stdout");

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

  if (!no_headers)
    {
     add_word_args (_("This is Info file %s, produced by Makeinfo version %d.%d"),
                     output_filename, major_version, minor_version);
     add_word_args (_(" from the input file %s.\n"), input_filename);
    }

  close_paragraph ();
  reader_loop ();

finished:
  discard_insertions (0);
  close_paragraph ();
  flush_file_stack ();

#if defined (HAVE_MACROS)
  if (macro_expansion_output_stream)
    {
      fclose (macro_expansion_output_stream);
      if (errors_printed && !force
          && strcmp (macro_expansion_filename, "-") != 0
          && strcmp (macro_expansion_filename, "/dev/null") != 0)
        {
          fprintf (stderr, _("%s: Removing macro output file `%s' due to errors; use --force to preserve.\n"),
                   progname, macro_expansion_filename);
          if (unlink (macro_expansion_filename) < 0)
            perror (macro_expansion_filename);
        }
    }
#endif /* HAVE_MACROS */

  if (output_stream)
    {
      output_pending_notes ();
      free_pending_notes ();
      if (tag_table != NULL)
        {
          tag_table = (TAG_ENTRY *) reverse_list (tag_table);
          if (!no_headers)
            write_tag_table ();
        }

      if (output_stream != stdout)
        fclose (output_stream);

      /* If validating, then validate the entire file right now. */
      if (validating)
        validate_file (tag_table);

      if (splitting && (!errors_printed || force))
        split_file (real_output_filename, 0);
      else if (errors_printed && !force
               && strcmp (real_output_filename, "-") != 0
               && strcmp (real_output_filename, "/dev/null") != 0)
        { /* If there were errors, and no --force, remove the output.  */
          fprintf (stderr, _("%s: Removing output file `%s' due to errors; use --force to preserve.\n"),
                   progname, real_output_filename);
          if (unlink (real_output_filename) < 0)
            perror (real_output_filename);
        }
    }
  free (real_output_filename);
}

void
free_and_clear (pointer)
     char **pointer;
{
  if (*pointer)
    {
      free (*pointer);
      *pointer = (char *) NULL;
    }
}

 /* Initialize some state. */
void
init_internals ()
{
  free_and_clear (&output_filename);
  free_and_clear (&command);
  free_and_clear (&input_filename);
  free_node_references ();
  init_insertion_stack ();
  init_brace_stack ();
  current_node = NULL; /* sometimes already freed */
  command_index = 0;
  in_menu = 0;
  in_detailmenu = 0;
  top_node_seen = 0;
  non_top_node_seen = 0;
}

void
init_paragraph ()
{
  free_and_clear (&output_paragraph);
  output_paragraph = (unsigned char *)xmalloc (paragraph_buffer_len);
  output_paragraph[0] = 0;
  output_paragraph_offset = 0;
  output_column = 0;
  paragraph_is_open = 0;
  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. */
void
reader_loop ()
{
  int character;
  int done = 0;
  int dash_count = 0;

  while (!done)
    {
      if (input_text_offset >= size_of_input_text)
        break;

      character = curchar ();

      if (!in_fixed_width_font &&
          (character == '\'' || character == '`') &&
          input_text[input_text_offset + 1] == character)
        {
          input_text_offset++;
          character = '"';
        }

      if (character == '-')
        {
          dash_count++;
          if (dash_count == 2 && !in_fixed_width_font)
            {
              input_text_offset++;
              continue;
            }
        }
      else
        {
          dash_count = 0;
        }

      /* If this is a whitespace character, then check to see if the line
         is blank.  If so, advance to the carriage return. */
      if (whitespace (character))
        {
          register int i = input_text_offset + 1;

          while (i < size_of_input_text && whitespace (input_text[i]))
            i++;

          if (i == size_of_input_text || input_text[i] == '\n')
            {
              if (i == size_of_input_text)
                i--;

              input_text_offset = i;
              character = curchar ();
            }
        }

      if (character == '\n')
        {
          line_number++;

          /* Check for a menu entry here, since the "escape sequence"
             that begins menu entries is "\n* ". */
          if (in_menu && input_text_offset + 1 < size_of_input_text)
            {
              char *glean_node_from_menu (), *tem;

              /* Note that the value of TEM is discarded, since it is
                 gauranteed to be NULL when glean_node_from_menu () is
                 called with a Nonzero argument. */
              if (!in_detailmenu)
                tem = glean_node_from_menu (1);
            }
        }

      switch (character)
        {
        case COMMAND_PREFIX:
          read_command ();
          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 %c"), '{');
          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++;
        }
    }
#if defined (HAVE_MACROS)
  if (macro_expansion_output_stream)
    maybe_write_itext (input_text, input_text_offset);
#endif /* HAVE_MACROS */
}

/* 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; command_table[i].name; i++)
    if (strcmp (command_table[i].name, string) == 0)
      return (&command_table[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]);

  /* 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. */
void
read_command ()
{
  COMMAND *entry;

  input_text_offset++;
  free_and_clear (&command);
  command = read_token ();

#if defined (HAVE_MACROS)
  /* Check to see if this command is a macro.  If so, execute it here. */
  {
    MACRO_DEF *def;

    def = find_macro (command);

    if (def)
      {
        /* We disallow recursive use of a macro call.  Inhibit the expansion
           of this macro during the life of its execution. */
        if (!(def->flags & ME_RECURSE))
          def->inhibited = 1;

        execute_macro (def);

        if (!(def->flags & ME_RECURSE))
          def->inhibited = 0;

        return;
      }
    }
#endif /* HAVE_MACROS */

  entry = get_command_entry (command);
  if (entry == (COMMAND *)-1)
    {
      line_error (_("Unknown command `%s'"), command);
      return;
    }

  if (entry->argument_in_braces)
    remember_brace (entry->proc);

  (*(entry->proc)) (START, output_paragraph_offset, 0);
}

/* Return the string which invokes PROC; a pointer to a function. */
char *
find_proc_name (proc)
     COMMAND_FUNCTION *proc;
{
  register int i;

  for (i = 0; command_table[i].name; i++)
    if (proc == command_table[i].proc)
      return command_table[i].name;
  return _("NO_NAME!");
}

void
init_brace_stack ()
{
  brace_stack = (BRACE_ELEMENT *) NULL;
}

void
remember_brace (proc)
     COMMAND_FUNCTION *proc;
{
  if (curchar () != '{')
    line_error (_("%c%s expected `{...}'"), COMMAND_PREFIX, 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. */
void
remember_brace_1 (proc, position)
     COMMAND_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;
  new->in_fixed_width_font = in_fixed_width_font;
  brace_stack = new;
}

/* Pop the top of the brace stack, and call the associated function
   with the args END and POS. */
void
pop_and_call_brace ()
{
  BRACE_ELEMENT *temp;
  COMMAND_FUNCTION *proc;
  int pos;

  if (brace_stack == (BRACE_ELEMENT *) NULL)
    {
      line_error (_("Unmatched }"));
      return;
    }

  pos = brace_stack->pos;
  proc = brace_stack->proc;
  in_fixed_width_font = brace_stack->in_fixed_width_font;
  temp = brace_stack->next;
  free (brace_stack);
  brace_stack = temp;

  (*proc) (END, pos, output_paragraph_offset);
}

/* Shift all of the markers in `brace_stack' by AMOUNT. */
void
adjust_braces_following (here, amount)
     int here, amount;
{
  register BRACE_ELEMENT *stack = brace_stack;

  while (stack)
    {
      if (stack->pos >= here)
        stack->pos += amount;
      stack = stack->next;
    }
}

/* 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. */
void
discard_braces ()
{
  if (!brace_stack)
    return;

  while (brace_stack)
    {
      if (brace_stack->proc != misplaced_brace)
        {
          char *proc_name;
          int temp_line_number = line_number;

          line_number = brace_stack->line;
          proc_name = find_proc_name (brace_stack->proc);
          line_error (_("%c%s missing close brace"), COMMAND_PREFIX, proc_name);
          line_number = temp_line_number;
          pop_and_call_brace ();
        }
      else
        {
          BRACE_ELEMENT *temp;
          temp = brace_stack->next;
          free (brace_stack);
          brace_stack = temp;
        }
    }
}

int
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:
      /* ASCII control characters appear as two characters in the output
         (e.g., ^A).  But characters with the high bit set are just one
         on suitable terminals, so don't count them as two for line
         breaking purposes.  */
      if (0 <= character && character < ' ')
        len = 2;
      else
        len = 1;
    }
  return (len);
}

void
#if defined (VA_FPRINTF) && __STDC__
add_word_args (char *format, ...)
#else
add_word_args (format, va_alist)
    char *format;
    va_dcl
#endif
{
  char buffer[1000];
#ifdef VA_FPRINTF
  va_list ap;
#endif

  VA_START (ap, format);
#ifdef VA_SPRINTF
  VA_SPRINTF (buffer, format, ap);
#else
  sprintf (buffer, format, a1, a2, a3, a4, a5, a6, a7, a8);
#endif /* not VA_SPRINTF */
  va_end (ap);
  add_word (buffer);
}

/* Add STRING to output_paragraph. */
void
add_word (string)
     char *string;
{
  while (*string)
    add_char (*string++);
}

/* Nonzero if the last character inserted has the syntax class of NEWLINE. */
int last_char_was_newline = 1;

/* The actual last inserted character.  Note that this may be something
   other than NEWLINE even if last_char_was_newline is 1. */
int last_inserted_character = 0;

/* Nonzero means that a newline character has already been
   inserted, so close_paragraph () should insert one less. */
int line_already_broken = 0;

/* When nonzero we have finished an insertion (see `end_insertion') and we
   want to ignore false continued paragraph closings. */
int insertion_paragraph_closed = 0;

/* Nonzero means attempt to make all of the lines have fill_column width. */
int do_justification = 0;

/* Add the character to the current paragraph.  If filling_enabled is
   nonzero, then do filling as well. */
void
add_char (character)
     int character;
{
  /* If we are avoiding outputting headers, and we are currently
     in a menu, then simply return. */
  if (no_headers && (in_menu || in_detailmenu))
    return;

  /* If we are adding a character now, then we don't have to
     ignore close_paragraph () calls any more. */
  if (must_start_paragraph && character != '\n')
    {
      must_start_paragraph = 0;
      line_already_broken = 0;  /* The line is no longer broken. */
      if (current_indent > output_column)
        {
          indent (current_indent - output_column);
          output_column = current_indent;
        }
    }

  if (non_splitting_words && member (character, " \t\n"))
    character = ' ' | 0x80;

  insertion_paragraph_closed = 0;

  switch (character)
    {
    case '\n':
      if (!filling_enabled)
        {
          insert ('\n');

          if (force_flush_right)
            {
              close_paragraph ();
              /* Hack to force single blank lines out in this mode. */
              flush_output ();
            }

          output_column = 0;

          if (!no_indent && paragraph_is_open)
            indent (output_column = current_indent);
          break;
        }
      else /* CHARACTER is newline, and filling is enabled. */
        {
          if (end_of_sentence_p ())
            {
              insert (' ');
              output_column++;
              last_inserted_character = character;
            }
        }

      if (last_char_was_newline)
        {
          close_paragraph ();
          pending_indent = 0;
        }
      else
        {
          last_char_was_newline = 1;
          insert (' ');
          output_column++;
        }
      break;

    default:
      {
        int len = get_char_len (character);
        int suppress_insert = 0;

        if ((character == ' ') && (last_char_was_newline))
          {
            if (!paragraph_is_open)
              {
                pending_indent++;
                return;
              }
          }

        if (!paragraph_is_open)
          {
            start_paragraph ();

            /* If the paragraph is supposed to be indented a certain way,
               then discard all of the pending whitespace.  Otherwise, we
               let the whitespace stay. */
            if (!paragraph_start_indent)
              indent (pending_indent);
            pending_indent = 0;
          }

        if ((output_column += len) > fill_column)
          {
            if (filling_enabled)
              {
                int temp = output_paragraph_offset;
                while (--temp > 0 && output_paragraph[temp] != '\n')
                  {
                    /* If we have found a space, we have the place to break
                       the line. */
                    if (output_paragraph[temp] == ' ')
                      {
                        /* Remove trailing whitespace from output. */
                        while (temp && whitespace (output_paragraph[temp - 1]))
                          temp--;

                        output_paragraph[temp++] = '\n';

                        /* We have correctly broken the line where we want
                           to.  What we don't want is spaces following where
                           we have decided to break the line.  We get rid of
                           them. */
                        {
                          int t1 = temp;

                          for (;; t1++)
                            {
                              if (t1 == output_paragraph_offset)
                                {
                                  if (whitespace (character))
                                    suppress_insert = 1;
                                  break;
                                }
                              if (!whitespace (output_paragraph[t1]))
                                break;
                            }

                          if (t1 != temp)
                            {
                              adjust_braces_following (temp, (- (t1 - temp)));
                              strncpy ((char *) &output_paragraph[temp],
                                       (char *) &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 = (char *)xmalloc (buffer_len);
                            int indentation = 0;

                            /* We have to shift any markers that are in
                               front of the wrap point. */
                            adjust_braces_following (temp, current_indent);

                            while (current_indent > 0 &&
                                   indentation != current_indent)
                              temp_buffer[indentation++] = ' ';

                            strncpy ((char *) &temp_buffer[current_indent],
                                     (char *) &output_paragraph[temp],
                                     buffer_len - current_indent);

                            if (output_paragraph_offset + buffer_len
                                >= paragraph_buffer_len)
                              {
                                unsigned char *tt = xrealloc
                                  (output_paragraph,
                                   (paragraph_buffer_len += buffer_len));
                                output_paragraph = tt;
                              }
                            strncpy ((char *) &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;
                      }
                  }
              }
          }

        if (!suppress_insert)
          {
            insert (character);
            last_inserted_character = character;
          }
        last_char_was_newline = 0;
        line_already_broken = 0;
      }
    }
}

/* Insert CHARACTER into `output_paragraph'. */
void
insert (character)
     int character;
{
  output_paragraph[output_paragraph_offset++] = character;
  if (output_paragraph_offset == paragraph_buffer_len)
    {
      output_paragraph =
        xrealloc (output_paragraph, (paragraph_buffer_len += 100));
    }
}

/* Insert the null-terminated string STRING into `output_paragraph'.  */
void
insert_string (string)
     char *string;
{
  while (*string)
    insert (*string++);
}


/* Sentences might have these characters after the period (or whatever).  */
#define post_sentence(c) ((c) == ')' || (c) == '\'' || (c) == '"' \
                          || (c) == ']')

/* Return true if at an end-of-sentence character, possibly followed by
   post-sentence punctuation to ignore.  */
static int
end_of_sentence_p ()
{
  int loc = output_paragraph_offset - 1;
  while (loc > 0 && post_sentence (output_paragraph[loc]))
    loc--;
  return sentence_ender (output_paragraph[loc]);
}


/* Remove up to COUNT characters of whitespace from the
   current output line.  If COUNT is less than zero,
   then remove until none left. */
void
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;
    }
}

/* Nonzero means do not honor calls to flush_output (). */
static int flushing_ignored = 0;

/* Prevent calls to flush_output () from having any effect. */
void
inhibit_output_flushing ()
{
  flushing_ignored++;
}

/* Allow calls to flush_output () to write the paragraph data. */
void
uninhibit_output_flushing ()
{
  flushing_ignored--;
}

void
flush_output ()
{
  register int i;

  if (!output_paragraph_offset || flushing_ignored)
    return;

  for (i = 0; i < output_paragraph_offset; i++)
    {
      /* If we turned on the 8th bit for a space
         inside @w, turn it back off for output.  */
      if (output_paragraph[i] & meta_character_bit)
        {
          int temp = UNMETA (output_paragraph[i]);
          if (temp == ' ')
            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 = DEFAULT_PARAGRAPH_SPACING;

/* Close the current paragraph, leaving no blank lines between them. */
void
close_single_paragraph ()
{
  close_paragraph_with_lines (0);
}

/* Close a paragraph after an insertion has ended. */
void
close_insertion_paragraph ()
{
  if (!insertion_paragraph_closed)
    {
      /* Close the current paragraph, breaking the line. */
      close_single_paragraph ();

      /* Start a new paragraph, with the correct indentation for the now
         current insertion level (one above the one that we are ending). */
      start_paragraph ();

      /* Tell `close_paragraph' that the previous line has already been
         broken, so it should insert one less newline. */
      line_already_broken = 1;

      /* Tell functions such as `add_char' we've already found a newline. */
      ignore_blank_line ();
    }
  else
    {
      /* If the insertion paragraph is closed already, then we are seeing
         two `@end' commands in a row.  Note that the first one we saw was
         handled in the first part of this if-then-else clause, and at that
         time `start_paragraph' was called, partially to handle the proper
         indentation of the current line.  However, the indentation level
         may have just changed again, so we may have to outdent the current
         line to the new indentation level. */
      if (current_indent < output_column)
        kill_self_indent (output_column - current_indent);
    }

  insertion_paragraph_closed = 1;
}

void
close_paragraph_with_lines (lines)
     int lines;
{
  int old_spacing = paragraph_spacing;
  paragraph_spacing = lines;
  close_paragraph ();
  paragraph_spacing = old_spacing;
}

/* Close the currently open paragraph. */
void
close_paragraph ()
{
  register int i;

  /* The insertion paragraph is no longer closed. */
  insertion_paragraph_closed = 0;

  if (paragraph_is_open && !must_start_paragraph)
    {
      register int tindex, c;

      tindex = output_paragraph_offset;

      /* Back up to last non-newline/space character, forcing all such
         subsequent characters to be newlines.  This isn't strictly
         necessary, but a couple of functions use the presence of a newline
         to make decisions. */
      for (tindex = output_paragraph_offset - 1; tindex >= 0; --tindex)
        {
          c = output_paragraph[tindex];

          if (c == ' '|| c == '\n')
            output_paragraph[tindex] = '\n';
          else
            break;
        }

      /* All trailing whitespace is ignored. */
      output_paragraph_offset = ++tindex;

      /* Break the line if that is appropriate. */
      if (paragraph_spacing >= 0)
        insert ('\n');

      /* Add as many blank lines as is specified in `paragraph_spacing'. */
      if (!force_flush_right)
        {
          for (i = 0; i < (paragraph_spacing - line_already_broken); i++)
            insert ('\n');
        }

      /* If we are doing flush right indentation, then do it now
         on the paragraph (really a single line). */
      if (force_flush_right)
        do_flush_right_indentation ();

      flush_output ();
      paragraph_is_open = 0;
      no_indent = 0;
      output_column = 0;
    }
  ignore_blank_line ();
}

/* Make the last line just read look as if it were only a newline. */
void
ignore_blank_line ()
{
  last_inserted_character = '\n';
  last_char_was_newline = 1;
}

/* Align the end of the text in output_paragraph with fill_column. */
void
do_flush_right_indentation ()
{
  char *temp;
  int temp_len;

  kill_self_indent (-1);

  if (output_paragraph[0] != '\n')
    {
      output_paragraph[output_paragraph_offset] = 0;

      if (output_paragraph_offset < fill_column)
        {
          register int i;

          if (fill_column >= paragraph_buffer_len)
            output_paragraph =
              xrealloc (output_paragraph,
                        (paragraph_buffer_len += fill_column));

          temp_len = strlen ((char *)output_paragraph);
          temp = (char *)xmalloc (temp_len + 1);
          memcpy (temp, (char *)output_paragraph, temp_len);

          for (i = 0; i < fill_column - output_paragraph_offset; i++)
            output_paragraph[i] = ' ';

          memcpy ((char *)output_paragraph + i, temp, temp_len);
          free (temp);
          output_paragraph_offset = fill_column;
        }
    }
}

/* Begin a new paragraph. */
void
start_paragraph ()
{
  /* First close existing one. */
  if (paragraph_is_open)
    close_paragraph ();

  /* In either case, the insertion paragraph is no longer closed. */
  insertion_paragraph_closed = 0;

  /* However, the paragraph is open! */
  paragraph_is_open = 1;

  /* If we MUST_START_PARAGRAPH, that simply means that start_paragraph ()
     had to be called before we would allow any other paragraph operations
     to have an effect. */
  if (!must_start_paragraph)
    {
      int amount_to_indent = 0;

      /* If doing indentation, then insert the appropriate amount. */
      if (!no_indent)
        {
          if (inhibit_paragraph_indentation)
            {
              amount_to_indent = current_indent;
              if (inhibit_paragraph_indentation < 0)
                inhibit_paragraph_indentation++;
            }
          else if (paragraph_start_indent < 0)
            amount_to_indent = current_indent;
          else
            amount_to_indent = current_indent + paragraph_start_indent;

          if (amount_to_indent >= output_column)
            {
              amount_to_indent -= output_column;
              indent (amount_to_indent);
              output_column += amount_to_indent;
            }
        }
    }
  else
    must_start_paragraph = 0;
}

/* Insert the indentation specified by AMOUNT. */
void
indent (amount)
     int amount;
{
  register BRACE_ELEMENT *elt = brace_stack;

  /* For every START_POS saved within the brace stack which will be affected
     by this indentation, bump that start pos forward. */
  while (elt)
    {
      if (elt->pos >= output_paragraph_offset)
        elt->pos += amount;
      elt = elt->next;
    }

  while (--amount >= 0)
    insert (' ');
}

/* Search forward for STRING in input_text.
   FROM says where to start. */
int
search_forward (string, from)
     char *string;
     int from;
{
  int len = strlen (string);

  while (from < size_of_input_text)
    {
      if (strncmp (input_text + from, string, len) == 0)
        return (from);
      from++;
    }
  return (-1);
}

/* Whoops, Unix doesn't have strcasecmp. */

/* Case independent string compare. */
#if !defined (HAVE_STRCASECMP)
int
strcasecmp (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 (ch1 - ch2);
    }
}
#endif /* !HAVE_STRCASECMP */

void
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 ()
{
  register int level, done;
  register INSERTION_ELT *elt;

  level = insertion_level;
  elt = insertion_stack;
  done = 0;

  /* Skip down through the stack until we find a non-conditional insertion. */
  while (!done && (elt != NULL))
    {
      switch (elt->insertion)
        {
        case ifinfo:
        case ifnothtml:
        case ifnottex:
        case ifset:
        case ifclear:
        case cartouche:
          elt = elt->next;
          level--;
          break;

        default:
          done = 1;
        }
    }

  if (!level)
    return ((char *) NULL);
  else
    return (elt->item_function);
}

char *
get_item_function ()
{
  char *item_function;
  get_rest_of_line (&item_function);
  backup_input_pointer ();
  return (item_function);
}

 /* Push the state of the current insertion on the stack. */
void
push_insertion (type, item_function)
     enum insertion_type type;
     char *item_function;
{
  INSERTION_ELT *new = (INSERTION_ELT *) xmalloc (sizeof (INSERTION_ELT));

  new->item_function = item_function;
  new->filling_enabled = filling_enabled;
  new->indented_fill = indented_fill;
  new->insertion = type;
  new->line_number = line_number;
  new->filename = xstrdup (input_filename);
  new->inhibited = inhibit_paragraph_indentation;
  new->in_fixed_width_font = in_fixed_width_font;
  new->next = insertion_stack;
  insertion_stack = new;
  insertion_level++;
}

 /* Pop the value on top of the insertion stack into the
    global variables. */
void
pop_insertion ()
{
  INSERTION_ELT *temp = insertion_stack;

  if (temp == (INSERTION_ELT *) NULL)
    return;

  in_fixed_width_font = temp->in_fixed_width_font;
  inhibit_paragraph_indentation = temp->inhibited;
  filling_enabled = temp->filling_enabled;
  indented_fill = temp->indented_fill;
  free_and_clear (&(temp->item_function));
  free_and_clear (&(temp->filename));
  insertion_stack = insertion_stack->next;
  free (temp);
  insertion_level--;
}

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

/* Return the insertion_type associated with NAME.
   If the type is not one of the known ones, return BAD_TYPE. */
enum insertion_type
find_type_from_name (name)
     char *name;
{
  int index = 0;
  while (index < (int) bad_type)
    {
      if (strcmp (name, insertion_type_names[index]) == 0)
        return (enum insertion_type) index;
      index++;
    }
  return (bad_type);
}

int
defun_insertion (type)
     enum insertion_type type;
{
  return
    ((type == deffn)
     || (type == defun)
     || (type == defmac)
     || (type == defspec)
     || (type == defvr)
     || (type == defvar)
     || (type == defopt)
     || (type == deftypefn)
     || (type == deftypefun)
     || (type == deftypevr)
     || (type == deftypevar)
     || (type == defcv)
     || (type == defivar)
     || (type == defop)
     || (type == defmethod)
     || (type == deftypemethod)
     || (type == deftp));
}

/* MAX_NS is the maximum nesting level for enumerations.  I picked 100
   which seemed reasonable.  This doesn't control the number of items,
   just the number of nested lists. */
#define max_stack_depth 100
#define ENUM_DIGITS 1
#define ENUM_ALPHA  2
typedef struct {
  int enumtype;
  int enumval;
} DIGIT_ALPHA;

DIGIT_ALPHA enumstack[max_stack_depth];
int enumstack_offset = 0;
int current_enumval = 1;
int current_enumtype = ENUM_DIGITS;
char *enumeration_arg = (char *)NULL;

void
start_enumerating (at, type)
     int at, type;
{
  if ((enumstack_offset + 1) == max_stack_depth)
    {
      line_error (_("Enumeration stack overflow"));
      return;
    }
  enumstack[enumstack_offset].enumtype = current_enumtype;
  enumstack[enumstack_offset].enumval = current_enumval;
  enumstack_offset++;
  current_enumval = at;
  current_enumtype = type;
}

void
stop_enumerating ()
{
  --enumstack_offset;
  if (enumstack_offset < 0)
    enumstack_offset = 0;

  current_enumval = enumstack[enumstack_offset].enumval;
  current_enumtype = enumstack[enumstack_offset].enumtype;
}

/* Place a letter or digits into the output stream. */
void
enumerate_item ()
{
  char temp[10];

  if (current_enumtype == ENUM_ALPHA)
    {
      if (current_enumval == ('z' + 1) || current_enumval == ('Z' + 1))
        {
          current_enumval = ((current_enumval - 1) == 'z' ? 'a' : 'A');
          warning (_("lettering overflow, restarting at %c"), current_enumval);
        }
      sprintf (temp, "%c. ", current_enumval);
    }
  else
    sprintf (temp, "%d. ", current_enumval);

  indent (output_column += (current_indent - strlen (temp)));
  add_word (temp);
  current_enumval++;
}

/* 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. */
void
begin_insertion (type)
     enum insertion_type type;
{
  int no_discard = 0;

  if (defun_insertion (type))
    {
      push_insertion (type, xstrdup (""));
      no_discard++;
    }
  else
    push_insertion (type, get_item_function ());

  switch (type)
    {
    case menu:
      if (!no_headers)
        close_paragraph ();

      filling_enabled = no_indent = 0;
      inhibit_paragraph_indentation = 1;

      if (!no_headers)
        add_word (_("* Menu:\n"));

      in_menu++;
      no_discard++;
      break;

    case detailmenu:
      if (!in_menu)
        {
          if (!no_headers)
            close_paragraph ();

          filling_enabled = no_indent = 0;
          inhibit_paragraph_indentation = 1;

          no_discard++;
        }

      in_detailmenu++;
      break;

    case direntry:
      close_single_paragraph ();
      filling_enabled = no_indent = 0;
      inhibit_paragraph_indentation = 1;
      insert_string ("START-INFO-DIR-ENTRY\n");
      break;

      /* I think @quotation is meant to do filling.
         If you don't want filling, then use @display. */
    case quotation:
      close_single_paragraph ();
      last_char_was_newline = no_indent = 0;
      indented_fill = filling_enabled = 1;
      inhibit_paragraph_indentation = 1;
      current_indent += default_indentation_increment;
      break;

    case display:
    case example:
    case smallexample:
    case lisp:
    case smalllisp:
      /* Just like @example, but no indentation. */
    case format:
      close_single_paragraph ();
      inhibit_paragraph_indentation = 1;
      in_fixed_width_font++;
      filling_enabled = 0;
      last_char_was_newline = 0;
      if (type != format)
        current_indent += default_indentation_increment;
      break;

    case multitable:
      do_multitable ();
      break;

    case table:
    case ftable:
    case vtable:
    case itemize:
      close_single_paragraph ();
      current_indent += default_indentation_increment;
      filling_enabled = indented_fill = 1;
#if defined (INDENT_PARAGRAPHS_IN_TABLE)
      inhibit_paragraph_indentation = 0;
#else
      inhibit_paragraph_indentation = 1;
#endif /* !INDENT_PARAGRAPHS_IN_TABLE */

      /* Make things work for losers who forget the itemize syntax. */
      if (allow_lax_format && (type == itemize))
        {
          if (!(*insertion_stack->item_function))
            {
              free (insertion_stack->item_function);
              insertion_stack->item_function = xstrdup ("@bullet");
              insertion_stack->item_function[0] = COMMAND_PREFIX;
            }
        }

      if (!*insertion_stack->item_function)
        {
          line_error (_("%s requires an argument: the formatter for %citem"),
                      insertion_type_pname (type), COMMAND_PREFIX);
        }
      break;

    case enumerate:
      close_single_paragraph ();
      no_indent = 0;
#if defined (INDENT_PARAGRAPHS_IN_TABLE)
      inhibit_paragraph_indentation = 0;
#else
      inhibit_paragraph_indentation = 1;
#endif /* !INDENT_PARAGRAPHS_IN_TABLE */

      current_indent += default_indentation_increment;
      filling_enabled = indented_fill = 1;

      if (isdigit (*enumeration_arg))
        start_enumerating (atoi (enumeration_arg), ENUM_DIGITS);
      else
        start_enumerating (*enumeration_arg, ENUM_ALPHA);
      break;

      /* Does nothing special in makeinfo. */
    case group:
      /* Only close the paragraph if we are not inside of an @example. */
      if (!insertion_stack->next ||
          insertion_stack->next->insertion != example)
        close_single_paragraph ();
      break;

      /* Insertions that are no-ops in info, but do something in TeX. */
    case ifinfo:
    case ifnothtml:
    case ifnottex:
    case ifset:
    case ifclear:
    case cartouche:
      if (in_menu)
        no_discard++;
      break;

    case deffn:
    case defun:
    case defmac:
    case defspec:
    case defvr:
    case defvar:
    case defopt:
    case deftypefn:
    case deftypefun:
    case deftypevr:
    case deftypevar:
    case defcv:
    case defivar:
    case defop:
    case defmethod:
    case deftypemethod:
    case deftp:
      inhibit_paragraph_indentation = 1;
      filling_enabled = indented_fill = 1;
      current_indent += default_indentation_increment;
      no_indent = 0;
      break;

    case flushleft:
      close_single_paragraph ();
      inhibit_paragraph_indentation = 1;
      filling_enabled = indented_fill = no_indent = 0;
      break;

    case flushright:
      close_single_paragraph ();
      filling_enabled = indented_fill = no_indent = 0;
      inhibit_paragraph_indentation = 1;
      force_flush_right++;
      break;
    }

  if (!no_discard)
    discard_until ("\n");
}

/* Try to end the insertion with the specified TYPE.  With a value of
   `bad_type', TYPE gets translated to match the value currently on top
   of the stack.  Otherwise, if TYPE doesn't match the top of the
   insertion stack, give error. */
void
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
        (_("`%cend' expected `%s', but saw `%s'"), COMMAND_PREFIX,
         insertion_type_pname (temp_type), insertion_type_pname (type));
      return;
    }

  pop_insertion ();

  switch (type)
    {
      /* Insertions which have no effect on paragraph formatting. */
    case ifnothtml:
    case ifnottex:
    case ifinfo:
    case ifset:
    case ifclear:
      break;

    case direntry:
      insert_string ("END-INFO-DIR-ENTRY\n\n");
      close_insertion_paragraph ();
      break;

    case detailmenu:
      in_detailmenu--;          /* No longer hacking menus. */
      if (!in_menu)
        {
          if (!no_headers)
            close_insertion_paragraph ();
        }
      break;

    case menu:
      in_menu--;                /* No longer hacking menus. */
      if (!no_headers)
        close_insertion_paragraph ();
      break;

    case multitable:
      end_multitable ();
      break;

    case enumerate:
      stop_enumerating ();
      close_insertion_paragraph ();
      current_indent -= default_indentation_increment;
      break;

    case flushleft:
    case group:
    case cartouche:
      close_insertion_paragraph ();
      break;

    case format:
    case display:
    case example:
    case smallexample:
    case lisp:
    case smalllisp:
    case quotation:
      /* @format is the only fixed_width insertion without a change
         in indentation. */
      if (type != format)
        current_indent -= default_indentation_increment;

      /* The ending of one of these insertions always marks the
         start of a new paragraph. */
      close_insertion_paragraph ();
      break;

    case table:
    case ftable:
    case vtable:
    case itemize:
      current_indent -= default_indentation_increment;
      break;

    case flushright:
      force_flush_right--;
      close_insertion_paragraph ();
      break;

      /* Handle the @defun style insertions with a default clause. */
    default:
      current_indent -= default_indentation_increment;
      close_insertion_paragraph ();
      break;
    }
}

/* 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.  With nonzero SPECIALS_OK, allows unmatched
   ifinfo, ifset, ifclear, otherwise not.  */
void
discard_insertions (specials_ok)
    int specials_ok;
{
  int real_line_number = line_number;
  while (insertion_stack)
    {
      if (specials_ok && (insertion_stack->insertion == ifinfo
          || insertion_stack->insertion == ifset
          || insertion_stack->insertion == ifclear))
        break;
      else
        {
          char *offender = insertion_type_pname (insertion_stack->insertion);
          char *current_filename = input_filename;
          
          input_filename = insertion_stack->filename;
          line_number = insertion_stack->line_number;
          line_error (_("No matching `%cend %s'"), COMMAND_PREFIX, offender);
          input_filename = current_filename;
          pop_insertion ();
        }
    }
  line_number = real_line_number;
}

/* The Texinfo commands. */

/* Commands which insert their own names. */
void
insert_self (arg)
    int arg;
{
  if (arg == START)
    add_word (command);
}

void
insert_space (arg)
    int arg;
{
  if (arg == START)
    add_char (' ');
}

/* Force a line break in the output. */
void
cm_asterisk ()
{
  close_single_paragraph ();
  cm_noindent ();
}

/* Insert ellipsis. */
void
cm_dots (arg)
     int arg;
{
  if (arg == START)
    add_word ("...");
}

/* Insert ellipsis for sentence end. */
void
cm_enddots (arg)
     int arg;
{
  if (arg == START)
    add_word ("....");
}

void
cm_bullet (arg)
     int arg;
{
  if (arg == START)
    add_char ('*');
}

void
cm_minus (arg)
     int arg;
{
  if (arg == START)
    add_char ('-');
}

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

/* Copyright symbol.  */
void
cm_copyright (arg)
    int arg;
{
  if (arg == START)
    add_word ("(C)");
}

/* Accent commands that take explicit arguments.  */
void
cm_accent (arg)
    int arg;
{
  if (arg == START)
    {
      if (strcmp (command, "dotaccent") == 0)  /* overdot */
        add_char ('.');
      else if (strcmp (command, "H") == 0)     /* Hungarian umlaut */
        add_word ("''");
      else if (strcmp (command, "ringaccent") == 0)
        add_char ('*');
      else if (strcmp (command, "tieaccent") == 0)
        add_char ('[');
      else if (strcmp (command, "u") == 0)     /* breve */
        add_char ('(');
      else if (strcmp (command, "v") == 0)     /* hacek/check */
        add_char ('<');
    }
  else if (arg == END)
    {
      if (strcmp (command, "ubaraccent") == 0) /* underbar */
        add_char ('_');
      else if (strcmp (command, "udotaccent") == 0) /* underdot */
        add_word ("-.");
      else if (strcmp (command, ",") == 0)     /* cedilla */
        add_word (",");
    }
} 

/* Non-English letters/characters that don't insert themselves.  */
void
cm_special_char (arg)
{
  if (arg == START)
    {
      if ((*command == 'L' || *command == 'l'
           || *command == 'O' || *command == 'o')
          && command[1] == 0)
        {
          /* Lslash lslash Oslash oslash */
          add_char (*command);
          add_char ('/');
        }
      else if (strcmp (command, "exclamdown") == 0)
        add_char ('!');
      else if (strcmp (command, "pounds") == 0)
        add_char ('#');
      else if (strcmp (command, "questiondown") == 0)
        add_char ('?');
      else
        fprintf (stderr, _("How did @%s end up in cm_special_char?\n"), command);
    }
}

/* Dotless i or j.  */
void
cm_dotless (arg, start, end)
    int arg, start, end;
{
  if (arg == END)
    {
      if (output_paragraph[start] != 'i' && output_paragraph[start] != 'j')
        /* This error message isn't perfect if the argument is multiple
           characters, but it doesn't seem worth getting right.  */
        line_error (_("%c%s expects `i' or `j' as argument, not `%c'"),
                    COMMAND_PREFIX, command, output_paragraph[start]);

      else if (end - start != 1)
        line_error (_("%c%s expects a single character `i' or `j' as argument"),
                    COMMAND_PREFIX, command);

      /* We've already inserted the `i' or `j', so nothing to do.  */
    }
}

void
cm_today (arg)
     int arg;
{
  static char *months [12] =
    { N_("January"), N_("February"), N_("March"), N_("April"), N_("May"),
      N_("June"), N_("July"), N_("August"), N_("September"), N_("October"),
      N_("November"), N_("December") };
  if (arg == START)
    {
      time_t timer = time (0);
      struct tm *ts = localtime (&timer);
      add_word_args ("%d %s %d", ts->tm_mday, _(months[ts->tm_mon]),
                     ts->tm_year + 1900);
    }
}

void
cm_code (arg)
     int arg;
{
  extern int printing_index;

  if (arg == START)
    {
      in_fixed_width_font++;

      if (!printing_index)
        add_char ('`');
    }
  else
    {
      if (!printing_index)
        add_char ('\'');
    }
}

void
cm_kbd (arg)
     int arg;
{
  /* People use @kbd in an example to get the "user input" font.
     We don't want quotes in that case.  */
  if (!in_fixed_width_font)
    cm_code (arg);
}

void
cm_key (arg)
     int arg;
{
  add_char (arg == START ? '<' : '>');
}

/* Convert the character at position into a true control character. */
void
cm_ctrl (arg, start, end)
     int arg, start, end;
{
  /* Should we allow multiple character arguments?  I think yes. */
  if (arg == END)
    {
      register int i, character;
#if defined (NO_MULTIPLE_CTRL)
      if ((end - start) != 1)
        line_error (_("%c%s expects a single character as an argument"),
                    COMMAND_PREFIX, command);
      else
#endif
        for (i = start; i < end; i++)
          {
            character = output_paragraph[i];

            if (isletter (character))
              output_paragraph[i] = CTL (coerce_to_upper (character));
          }
    }
}

/* Handle a command that switches to a non-fixed-width font.  */
void
not_fixed_width (arg)
     int arg;
{
  if (arg == START)
    in_fixed_width_font = 0;
}

/* Small caps and @var in makeinfo just uppercase the text. */
void
cm_var_sc (arg, start_pos, end_pos)
     int arg, start_pos, end_pos;
{
  not_fixed_width (arg);

  if (arg == END)
    {
      while (start_pos < end_pos)
        {
          output_paragraph[start_pos] =
            coerce_to_upper (output_paragraph[start_pos]);
          start_pos++;
        }
    }
}

void
cm_dfn (arg, position)
     int arg, position;
{
  add_char ('"');
}

void
cm_emph (arg)
     int arg;
{
  add_char ('*');
}

void
cm_strong (arg, position)
     int arg, position;
{
  cm_emph (arg);
}

void
cm_cite (arg, position)
     int arg, position;
{
  if (arg == START)
    add_word ("`");
  else
    add_word ("'");
}

/* No highlighting, but argument switches fonts.  */
void
cm_not_fixed_width (arg, start, end)
     int arg, start, end;
{
  not_fixed_width (arg);
}

/* Various commands are no-op's. */
void
cm_no_op ()
{
}

/* No-op that eats its argument on same line.  */
void
cm_no_op_line_arg ()
{
  char *temp;
  get_rest_of_line (&temp);
  free (temp);
}

/* Prevent the argument from being split across two lines. */
void
cm_w (arg, start, end)
     int arg, start, end;
{
  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. */
void
cm_obsolete (arg, start, end)
     int arg, start, end;
{
  if (arg == START)
    warning (_("%c%s is obsolete"), COMMAND_PREFIX, 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. */
void
insert_and_underscore (with_char)
     int with_char;
{
  register int i, len;
  int old_no_indent, starting_pos, ending_pos;
  char *temp;

  close_paragraph ();
  filling_enabled =  indented_fill = 0;
  old_no_indent = no_indent;
  no_indent = 1;

#if defined (HAVE_MACROS)
  if (macro_expansion_output_stream && !executing_string)
    append_to_expansion_output (input_text_offset + 1);
#endif /* HAVE_MACROS */

  get_rest_of_line (&temp);

  starting_pos = output_position + output_paragraph_offset;
#if defined (HAVE_MACROS)
  if (macro_expansion_output_stream && !executing_string)
    {
      char *temp1 = (char *) xmalloc (2 + strlen (temp));
      sprintf (temp1, "%s\n", temp);
      remember_itext (input_text, input_text_offset);
      me_execute_string (temp1);
      free (temp1);
    }
  else
#endif /* HAVE_MACROS */
    execute_string ("%s\n", temp);

  ending_pos = output_position + output_paragraph_offset;
  free (temp);

  len = (ending_pos - starting_pos) - 1;
  for (i = 0; i < len; i++)
    add_char (with_char);
  insert ('\n');
  close_paragraph ();
  filling_enabled = 1;
  no_indent = old_no_indent;
}

/* Here is a structure which associates sectioning commands with
   an integer, hopefully to reflect the `depth' of the current
   section. */
struct {
  char *name;
  int level;
} section_alist[] = {
  { "unnumberedsubsubsec", 5 },
  { "unnumberedsubsec", 4 },
  { "unnumberedsec", 3 },
  { "unnumbered", 2 },
  { "appendixsubsubsec", 5 },
  { "appendixsubsec", 4 },
  { "appendixsec", 3 },
  { "appendixsection", 3 },
  { "appendix", 2 },
  { "subsubsec", 5 },
  { "subsubsection", 5 },
  { "subsection", 4 },
  { "section", 3 },
  { "chapter", 2 },
  { "top", 1 },

  { (char *)NULL, 0 }
};

/* Amount to offset the name of sectioning commands to levels by. */
int section_alist_offset = 0;

/* Shift the meaning of @section to @chapter. */
void
cm_raisesections ()
{
  discard_until ("\n");
  section_alist_offset--;
}

/* Shift the meaning of @chapter to @section. */
void
cm_lowersections ()
{
  discard_until ("\n");
  section_alist_offset++;
}

/* Return an integer which identifies the type section present in TEXT. */
int
what_section (text)
     char *text;
{
  register int i, j;
  char *t;

 find_section_command:
  for (j = 0; text[j] && cr_or_whitespace (text[j]); j++);
  if (text[j] != COMMAND_PREFIX)
    return (-1);

  text = text + j + 1;

  /* We skip @c, @comment, and @?index commands. */
  if ((strncmp (text, "comment", strlen ("comment")) == 0) ||
      (text[0] == 'c' && cr_or_whitespace (text[1])) ||
      (strcmp (text + 1, "index") == 0))
    {
      while (*text++ != '\n');
      goto find_section_command;
    }

  /* Handle italicized sectioning commands. */
  if (*text == 'i')
    text++;

  for (j = 0; text[j] && !cr_or_whitespace (text[j]); j++);

  for (i = 0; (t = section_alist[i].name); i++)
    {
      if (j == strlen (t) && strncmp (t, text, j) == 0)
        {
          int return_val;

          return_val = (section_alist[i].level + section_alist_offset);

          if (return_val < 0)
            return_val = 0;
          else if (return_val > 5)
            return_val = 5;
          return (return_val);
        }
    }
  return (-1);
}

/* Set the level of @top to LEVEL.  Return the old level of @top. */
int
set_top_section_level (level)
     int level;
{
  register int i, result = -1;

  for (i = 0; section_alist[i].name; i++)
    if (strcmp (section_alist[i].name, "top") == 0)
      {
        result = section_alist[i].level;
        section_alist[i].level = level;
        break;
      }
  return (result);
}

/* Treat this just like @unnumbered.  The only difference is
   in node defaulting. */
void
cm_top ()
{
  /* It is an error to have more than one @top. */
  if (top_node_seen)
    {
      TAG_ENTRY *tag = tag_table;

      line_error (_("Node with %ctop as a section already exists"),
                  COMMAND_PREFIX);

      while (tag != (TAG_ENTRY *)NULL)
        {
          if ((tag->flags & IS_TOP))
            {
              int old_line_number = line_number;
              char *old_input_filename = input_filename;

              line_number = tag->line_no;
              input_filename = tag->filename;
              line_error (_("Here is the %ctop node"), COMMAND_PREFIX);
              input_filename = old_input_filename;
              line_number = old_line_number;
              return;
            }
          tag = tag->next_ent;
        }
    }
  else
    {
      top_node_seen = 1;

      /* It is an error to use @top before you have used @node. */
      if (!tag_table)
        {
          char *top_name;

          get_rest_of_line (&top_name);
          free (top_name);
          line_error (_("%ctop used before %cnode, defaulting to %s"),
                      COMMAND_PREFIX, COMMAND_PREFIX, top_name);
          execute_string ("@node Top, , (dir), (dir)\n@top %s\n", top_name);
          return;
        }

      cm_unnumbered ();

      /* The most recently defined node is the top node. */
      tag_table->flags |= IS_TOP;

      /* Now set the logical hierarchical level of the Top node. */
      {
        int orig_offset = input_text_offset;

        input_text_offset = search_forward (node_search_string, orig_offset);

        if (input_text_offset > 0)
          {
            int this_section;

            /* We have encountered a non-top node, so mark that one exists. */
            non_top_node_seen = 1;

            /* Move to the end of this line, and find out what the
               sectioning command is here. */
            while (input_text[input_text_offset] != '\n')
              input_text_offset++;

            if (input_text_offset < size_of_input_text)
              input_text_offset++;

            this_section = what_section (input_text + input_text_offset);

            /* If we found a sectioning command, then give the top section
               a level of this section - 1. */
            if (this_section != -1)
              set_top_section_level (this_section - 1);
          }
        input_text_offset = orig_offset;
      }
    }
}

/* Organized by level commands.  That is, "*" == chapter, "=" == section. */
char *scoring_characters = "*=-.";

void
sectioning_underscore (command)
     char *command;
{
  char character;
  char *temp;
  int level;

  temp = (char *)xmalloc (2 + strlen (command));
  temp[0] = COMMAND_PREFIX;
  strcpy (&temp[1], command);
  level = what_section (temp);
  free (temp);
  level -= 2;

  if (level < 0)
    level = 0;

  character = scoring_characters[level];

  insert_and_underscore (character);
}

/* The command still works, but prints a warning message in addition. */
void
cm_ideprecated (arg, start, end)
     int arg, start, end;
{
  warning (_("%c%s is obsolete; use %c%s instead"),
           COMMAND_PREFIX, command, COMMAND_PREFIX, command + 1);
  sectioning_underscore (command + 1);
}

/* The remainder of the text on this line is a chapter heading. */
void
cm_chapter ()
{
  sectioning_underscore ("chapter");
}

/* The remainder of the text on this line is a section heading. */
void
cm_section ()
{
  sectioning_underscore ("section");
}

/* The remainder of the text on this line is a subsection heading. */
void
cm_subsection ()
{
  sectioning_underscore ("subsection");
}

/* The remainder of the text on this line is a subsubsection heading. */
void
cm_subsubsection ()
{
  sectioning_underscore ("subsubsection");
}

/* The remainder of the text on this line is an unnumbered heading. */
void
cm_unnumbered ()
{
  cm_chapter ();
}

/* The remainder of the text on this line is an unnumbered section heading. */
void
cm_unnumberedsec ()
{
  cm_section ();
}

/* The remainder of the text on this line is an unnumbered
   subsection heading. */
void
cm_unnumberedsubsec ()
{
  cm_subsection ();
}

/* The remainder of the text on this line is an unnumbered
   subsubsection heading. */
void
cm_unnumberedsubsubsec ()
{
  cm_subsubsection ();
}

/* The remainder of the text on this line is an appendix heading. */
void
cm_appendix ()
{
  cm_chapter ();
}

/* The remainder of the text on this line is an appendix section heading. */
void
cm_appendixsec ()
{
  cm_section ();
}

/* The remainder of the text on this line is an appendix subsection heading. */
void
cm_appendixsubsec ()
{
  cm_subsection ();
}

/* The remainder of the text on this line is an appendix
   subsubsection heading. */
void
cm_appendixsubsubsec ()
{
  cm_subsubsection ();
}

/* Compatibility functions substitute for chapter, section, etc. */
void
cm_majorheading ()
{
  cm_chapheading ();
}

void
cm_chapheading ()
{
  cm_chapter ();
}

void
cm_heading ()
{
  cm_section ();
}

void
cm_subheading ()
{
  cm_subsection ();
}

void
cm_subsubheading ()
{
  cm_subsubsection ();
}

/* **************************************************************** */
/*                                                                  */
/*                 Adding nodes, and making tags                    */
/*                                                                  */
/* **************************************************************** */

/* Start a new tag table. */
void
init_tag_table ()
{
  while (tag_table != (TAG_ENTRY *) NULL)
    {
      TAG_ENTRY *temp = tag_table;
      free (temp->node);
      free (temp->prev);
      free (temp->next);
      free (temp->up);
      tag_table = tag_table->next_ent;
      free (temp);
    }
}

void
write_tag_table ()
{
  write_tag_table_internal (0); /* Not indirect. */
}

void
write_tag_table_indirect ()
{
  write_tag_table_internal (1);
}

/* Write out the contents of the existing tag table.
   INDIRECT_P says how to format the output. */
void
write_tag_table_internal (indirect_p)
     int indirect_p;
{
  TAG_ENTRY *node = tag_table;
  int old_indent = no_indent;

  no_indent = 1;
  filling_enabled = 0;
  must_start_paragraph = 0;
  close_paragraph ();

  if (!indirect_p)
    {
      no_indent = 1;
      insert ('\n');
    }

  add_word_args ("\037\nTag Table:\n%s", indirect_p ? "(Indirect)\n" : "");

  while (node != (TAG_ENTRY *) NULL)
    {
      execute_string ("Node: %s", node->node);
      add_word_args ("\177%d\n", node->position);
      node = node->next_ent;
    }

  add_word ("\037\nEnd Tag Table\n");
  flush_output ();
  no_indent = old_indent;
}

char *
get_node_token (expand)
      int expand;
{
  char *string;

  get_until_in_line (expand, ",", &string);

  if (curchar () == ',')
    input_text_offset++;

  canon_white (string);

  /* Force all versions of "top" to be "Top". */
  normalize_node_name (string);

  return (string);
}

/* Convert "top" and friends into "Top". */
void
normalize_node_name (string)
     char *string;
{
  if (strcasecmp (string, "Top") == 0)
    strcpy (string, "Top");
}

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

  while (tag != (TAG_ENTRY *) NULL)
    {
      if (strcmp (tag->node, name) == 0)
        return (tag);
      tag = tag->next_ent;
    }
  return ((TAG_ENTRY *) NULL);
}

/* Remember NODE and associates. */
void
remember_node (node, prev, next, up, position, line_no, no_warn)
     char *node, *prev, *next, *up;
     int position, line_no, no_warn;
{
  /* Check for existence of this tag already. */
  if (validating)
    {
      register TAG_ENTRY *tag = find_node (node);
      if (tag)
        {
          line_error (
                 _("Node `%s' multiply defined (line %d is first definition at)"),
                      node, tag->line_no);
          return;
        }
    }

  /* First, make this the current node. */
  current_node = node;

  /* Now add it to the list. */
  {
    TAG_ENTRY *new = (TAG_ENTRY *) xmalloc (sizeof (TAG_ENTRY));
    new->node = node;
    new->prev = prev;
    new->next = next;
    new->up = up;
    new->position = position;
    new->line_no = line_no;
    new->filename = node_filename;
    new->touched = 0;           /* not yet referenced. */
    new->flags = 0;
    if (no_warn)
      new->flags |= NO_WARN;
    new->next_ent = tag_table;
    tag_table = new;
  }
}

/* The order is: nodename, nextnode, prevnode, upnode.
   If all of the NEXT, PREV, and UP fields are empty, they are defaulted.
   You must follow a node command which has those fields defaulted
   with a sectioning command (e.g. @chapter) giving the "level" of that node.
   It is an error not to do so.
   The defaults come from the menu in this node's parent. */
void
cm_node ()
{
  char *node, *prev, *next, *up;
  int new_node_pos, defaulting, this_section, no_warn = 0;
  extern int already_outputting_pending_notes;

  if (strcmp (command, "nwnode") == 0)
    no_warn = 1;

  /* Get rid of unmatched brace arguments from previous commands. */
  discard_braces ();

  /* There also might be insertions left lying around that haven't been
     ended yet.  Do that also. */
  discard_insertions (1);

  if (!already_outputting_pending_notes)
    {
      close_paragraph ();
      output_pending_notes ();
      free_pending_notes ();
    }

  filling_enabled = indented_fill = 0;
  new_node_pos = output_position;
  current_footnote_number = 1;

#if defined (HAVE_MACROS)
  if (macro_expansion_output_stream && !executing_string)
    append_to_expansion_output (input_text_offset + 1);
#endif /* HAVE_MACROS */

  node = get_node_token (1);
  next = get_node_token (0);
  prev = get_node_token (0);
  up = get_node_token (0);

  if (verbose_mode)
    printf (_("Formatting node %s...\n"), node);
    
#if defined (HAVE_MACROS)
  if (macro_expansion_output_stream && !executing_string)
    remember_itext (input_text, input_text_offset);
#endif /* HAVE_MACROS */

  no_indent = 1;
  if (!no_headers)
    {
      add_word_args ("\037\nFile: %s,  Node: ", pretty_output_filename);

#if defined (HAVE_MACROS)
      if (macro_expansion_output_stream && !executing_string)
        me_execute_string (node);
      else
#endif /* HAVE_MACROS */
        execute_string ("%s", node);
      filling_enabled = indented_fill = 0;
    }

  /* Check for defaulting of this node's next, prev, and up fields. */
  defaulting = (*next == 0 && *prev == 0 && *up == 0);

  this_section = what_section (input_text + input_text_offset);

  /* If we are defaulting, then look at the immediately following
     sectioning command (error if none) to determine the node's
     level.  Find the node that contains the menu mentioning this node
     that is one level up (error if not found).  That node is the "Up"
     of this node.  Default the "Next" and "Prev" from the menu. */
  if (defaulting)
    {
      NODE_REF *last_ref = (NODE_REF *)NULL;
      NODE_REF *ref = node_references;

      if ((this_section < 0) && (strcmp (node, "Top") != 0))
        {
          char *polite_section_name = "top";
          int i;

          for (i = 0; section_alist[i].name; i++)
            if (section_alist[i].level == current_section + 1)
              {
                polite_section_name = section_alist[i].name;
                break;
              }

          line_error
            (_("Node `%s' requires a sectioning command (e.g. %c%s)"),
             node, COMMAND_PREFIX, polite_section_name);
        }
      else
        {
          if (strcmp (node, "Top") == 0)
            {
              /* Default the NEXT pointer to be the first menu item in
                 this node, if there is a menu in this node.  We have to
                 try very hard to find the menu, as it may be obscured
                 by execution_strings which are on the filestack.  For
                 every member of the filestack which has a FILENAME
                 member which is identical to the current INPUT_FILENAME,
                 search forward from that offset. */
              int saved_input_text_offset = input_text_offset;
              int saved_size_of_input_text = size_of_input_text;
              char *saved_input_text = input_text;
              FSTACK *next_file = filestack;

              int orig_offset, orig_size;
              char *glean_node_from_menu ();

              /* No matter what, make this file point back at `(dir)'. */
              free (up);   up = xstrdup ("(dir)");

              while (1)
                {
                  orig_offset = input_text_offset;
                  orig_size =
                    search_forward (node_search_string, orig_offset);

                  if (orig_size < 0)
                    orig_size = size_of_input_text;

                  input_text_offset =
                    search_forward (menu_search_string, orig_offset);

                  if (input_text_offset > -1)
                    {
                      char *nodename_from_menu = (char *)NULL;

                      input_text_offset =
                        search_forward ("\n* ", input_text_offset);

                      if (input_text_offset != -1)
                        nodename_from_menu = glean_node_from_menu (0);

                      if (nodename_from_menu)
                        {
                          free (next); next = nodename_from_menu;
                          break;
                        }
                    }

                  /* We got here, so it hasn't been found yet.  Try
                     the next file on the filestack if there is one. */
                  if (next_file &&
                      (strcmp (next_file->filename, input_filename) == 0))
                    {
                      input_text = next_file->text;
                      input_text_offset = next_file->offset;
                      size_of_input_text = next_file->size;
                      next_file = next_file->next;
                    }
                  else
                    {
                      /* No more input files to check. */
                      break;
                    }
                }

              input_text = saved_input_text;
              input_text_offset = saved_input_text_offset;
              size_of_input_text = saved_size_of_input_text;
            }
        }

      /* Fix the level of the menu references in the Top node, iff it
         was declared with @top, and no subsequent reference was found. */
      if (top_node_seen && !non_top_node_seen)
        {
          /* Then this is the first non-@top node seen. */
          int level;

          level = set_top_section_level (this_section - 1);
          non_top_node_seen = 1;

          while (ref)
            {
              if (ref->section == level)
                ref->section = this_section - 1;
              ref = ref->next;
            }

          ref = node_references;
        }

      while (ref)
        {
          if (ref->section == (this_section - 1) &&
              ref->type == menu_reference &&
              strcmp (ref->node, node) == 0)
            {
              char *containing_node = ref->containing_node;

              free (up);
              up = xstrdup (containing_node);

              if (last_ref &&
                  last_ref->type == menu_reference &&
                  (strcmp (last_ref->containing_node,
                           containing_node) == 0))
                {
                  free (next);
                  next = xstrdup (last_ref->node);
                }

              while ((ref->section == this_section - 1) &&
                     (ref->next) &&
                     (ref->next->type != menu_reference))
                ref = ref->next;

              if (ref->next && ref->type == menu_reference &&
                  (strcmp (ref->next->containing_node,
                           containing_node) == 0))
                {
                  free (prev);
                  prev = xstrdup (ref->next->node);
                }
              else if (!ref->next &&
                       strcasecmp (ref->containing_node, "Top") == 0)
                {
                  free (prev);
                  prev = xstrdup (ref->containing_node);
                }
              break;
            }
          last_ref = ref;
          ref = ref->next;
        }
    }

#if defined (HAVE_MACROS)
  /* Insert the correct args if we are expanding macros, and the node's
     pointers weren't defaulted. */
  if (macro_expansion_output_stream && !executing_string && !defaulting)
    {
      char *temp;
      int op_orig = output_paragraph_offset;

      temp = (char *)xmalloc (3 + strlen (next));
      sprintf (temp, ", %s", next);
      me_execute_string (temp);
      free (temp);

      temp = (char *)xmalloc (3 + strlen (prev));
      sprintf (temp, ", %s", prev);
      me_execute_string (temp);
      free (temp);

      temp = (char *)xmalloc (4 + strlen (up));
      sprintf (temp, ", %s", up);
      me_execute_string (temp);
      free (temp);

      output_paragraph_offset = op_orig;
    }
#endif /* HAVE_MACROS */

  if (!no_headers)
    {
#if defined (HAVE_MACROS)
      if (macro_expansion_output_stream)
        me_inhibit_expansion++;
#endif /* HAVE_MACROS */

      if (*next)
        {
          execute_string (",  Next: %s", next);
          filling_enabled = indented_fill = 0;
        }

      if (*prev)
        {
          execute_string (",  Prev: %s", prev);
          filling_enabled = indented_fill = 0;
        }

      if (*up)
        {
          execute_string (",  Up: %s", up);
          filling_enabled = indented_fill = 0;
        }
#if defined (HAVE_MACROS)
      if (macro_expansion_output_stream)
        me_inhibit_expansion--;
#endif /* HAVE_MACROS */
    }

  close_paragraph ();
  no_indent = 0;

  if (!*node)
    {
      line_error ("No node name specified for `%c%s' command",
                  COMMAND_PREFIX, command);
      free (node);
      free (next);
      free (prev);
      free (up);
    }
  else
    {
      if (!*next) { free (next); next = (char *)NULL; }
      if (!*prev) { free (prev); prev = (char *)NULL; }
      if (!*up) { free (up); up = (char *)NULL; }
      remember_node (node, prev, next, up, new_node_pos, line_number, no_warn);
    }

  /* Change the section only if there was a sectioning command. */
  if (this_section >= 0)
    current_section = this_section;

  filling_enabled = 1;
}

/* Validation of an info file.
   Scan through the list of tag entries touching the Prev, Next, and Up
   elements of each.  It is an error not to be able to touch one of them,
   except in the case of external node references, such as "(DIR)".

   If the Prev is different from the Up,
   then the Prev node must have a Next pointing at this node.

   Every node except Top must have an Up.
   The Up node must contain some sort of reference, other than a Next,
   to this node.

   If the Next is different from the Next of the Up,
   then the Next node must have a Prev pointing at this node. */
void
validate_file (tag_table)
     TAG_ENTRY *tag_table;
{
  char *old_input_filename = input_filename;
  TAG_ENTRY *tags = tag_table;

  while (tags != (TAG_ENTRY *) NULL)
    {
      register TAG_ENTRY *temp_tag;

      input_filename = tags->filename;
      line_number = tags->line_no;

      /* If this is a "no warn" node, don't validate it in any way. */
      if (tags->flags & NO_WARN)
        {
          tags = tags->next_ent;
          continue;
        }

      /* If this node has a Next, then make sure that the Next exists. */
      if (tags->next)
        {
          validate (tags->next, tags->line_no, "Next");

          /* If the Next node exists, and there is no Up, then make
             sure that the Prev of the Next points back. */
          temp_tag = find_node (tags->next);
          if (temp_tag)
            {
              char *prev;

              if (temp_tag->flags & NO_WARN)
                {
                  /* Do nothing if we aren't supposed to issue warnings
                     about this node. */
                }
              else
                {
                  prev = temp_tag->prev;
                  if (!prev || (strcmp (prev, tags->node) != 0))
                    {
                      line_error (_("Node `%s''s Next field not pointed back to"),
                                  tags->node);
                      line_number = temp_tag->line_no;
                      input_filename = temp_tag->filename;
                      line_error
                        (_("This node (`%s') is the one with the bad `Prev'"),
                         temp_tag->node);
                      input_filename = tags->filename;
                      line_number = tags->line_no;
                      temp_tag->flags |= PREV_ERROR;
                    }
                }
            }
        }

      /* Validate the Prev field if there is one, and we haven't already
         complained about it in some way.  You don't have to have a Prev
         field at this stage. */
      if (!(tags->flags & PREV_ERROR) && tags->prev)
        {
          int valid_p = validate (tags->prev, tags->line_no, "Prev");

          if (!valid_p)
            tags->flags |= PREV_ERROR;
          else
            {
              /* If the Prev field is not the same as the Up field,
                 then the node pointed to by the Prev field must have
                 a Next field which points to this node. */
              if (tags->up && (strcmp (tags->prev, tags->up) != 0))
                {
                  temp_tag = find_node (tags->prev);

                  /* If we aren't supposed to issue warnings about the
                     target node, do nothing. */
                  if (!temp_tag || (temp_tag->flags & NO_WARN))
                    {
                      /* Do nothing. */
                    }
                  else
                    {
                      if (!temp_tag->next ||
                          (strcmp (temp_tag->next, tags->node) != 0))
                        {
                          line_error
                            (_("Node `%s's Prev field not pointed back to"),
                             tags->node);
                          line_number = temp_tag->line_no;
                          input_filename = temp_tag->filename;
                          line_error (_("This node (`%s') has the bad Next"),
                                      temp_tag->node);
                          input_filename = tags->filename;
                          line_number = tags->line_no;
                          temp_tag->flags |= NEXT_ERROR;
                        }
                    }
                }
            }
        }

      if (!tags->up && (strcasecmp (tags->node, _("Top")) != 0))
        line_error (_("Node `%s' missing Up field"), tags->node);
      else if (tags->up)
        {
          int valid_p = validate (tags->up, tags->line_no, "Up");

          /* If node X has Up: Y, then warn if Y fails to have a menu item
             or note pointing at X, if Y isn't of the form "(Y)". */
          if (valid_p && *tags->up != '(')
            {
              NODE_REF *nref, *tref, *list;
              NODE_REF *find_node_reference ();

              tref = (NODE_REF *) NULL;
              list = node_references;

              for (;;)
                {
                  if (!(nref = find_node_reference (tags->node, list)))
                    break;

                  if (strcmp (nref->containing_node, tags->up) == 0)
                    {
                      if (nref->type != menu_reference)
                        {
                          tref = nref;
                          list = nref->next;
                        }
                      else
                        break;
                    }
                  list = nref->next;
                }

              if (!nref)
                {
                  temp_tag = find_node (tags->up);
                  line_number = temp_tag->line_no;
                  input_filename = temp_tag->filename;
                  if (!tref)
                    line_error (
_("`%s' has an Up field of `%s', but `%s' has no menu item for `%s'"),
                                tags->node, tags->up, tags->up, tags->node);
                  line_number = tags->line_no;
                  input_filename = tags->filename;
                }
            }
        }
      tags = tags->next_ent;
    }

  validate_other_references (node_references);
  /* We have told the user about the references which didn't exist.
     Now tell him about the nodes which aren't referenced. */

  tags = tag_table;
  while (tags != (TAG_ENTRY *) NULL)
    {
      /* If this node is a "no warn" node, do nothing. */
      if (tags->flags & NO_WARN)
        {
          tags = tags->next_ent;
          continue;
        }

      /* Special hack.  If the node in question appears to have
         been referenced more than REFERENCE_WARNING_LIMIT times,
         give a warning. */
      if (tags->touched > reference_warning_limit)
        {
          input_filename = tags->filename;
          line_number = tags->line_no;
          warning (_("node `%s' has been referenced %d times"),
                   tags->node, tags->touched);
        }

      if (tags->touched == 0)
        {
          input_filename = tags->filename;
          line_number = tags->line_no;

          /* Notice that the node "Top" is special, and doesn't have to
             be referenced. */
          if (strcasecmp (tags->node, _("Top")) != 0)
            warning (_("unreferenced node `%s'"), tags->node);
        }
      tags = tags->next_ent;
    }
  input_filename = old_input_filename;
}

/* Return 1 if tag correctly validated, or 0 if not. */
int
validate (tag, line, label)
     char *tag;
     int line;
     char *label;
{
  TAG_ENTRY *result;

  /* If there isn't a tag to verify, or if the tag is in another file,
     then it must be okay. */
  if (!tag || !*tag || *tag == '(')
    return (1);

  /* Otherwise, the tag must exist. */
  result = find_node (tag);

  if (!result)
    {
      line_number = line;
      line_error (_("%s reference to nonexistent node `%s'"), label, tag);
      return (0);
    }
  result->touched++;
  return (1);
}

/* Split large output files into a series of smaller files.  Each file
   is pointed to in the tag table, which then gets written out as the
   original file.  The new files have the same name as the original file
   with a "-num" attached.  SIZE is the largest number of bytes to allow
   in any single split file. */
void
split_file (filename, size)
     char *filename;
     int size;
{
  char *root_filename, *root_pathname;
  char *the_file, *filename_part ();
  struct stat fileinfo;
  long file_size;
  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) ||
      (((long) fileinfo.st_size) < SPLIT_SIZE_THRESHOLD))
    return;
  file_size = (long) fileinfo.st_size;

  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 = xstrdup ("");

  /* Start splitting the file.  Walk along the tag table
     outputting sections of the file.  When we have written
     all of the nodes in the tag table, make the top-level
     pointer file, which contains indirect pointers and
     tags for the nodes. */
  {
    int which_file = 1;
    TAG_ENTRY *tags = tag_table;
    char *indirect_info = (char *)NULL;

    /* Remember the `header' of this file.  The first tag in the file is
       the bottom of the header; the top of the file is the start. */
    the_header = (char *)xmalloc (1 + (header_size = tags->position));
    memcpy (the_header, the_file, header_size);

    while (tags)
      {
        int file_top, file_bot, limit;

        /* Have to include the Control-_. */
        file_top = file_bot = tags->position;
        limit = file_top + size;

        /* If the rest of this file is only one node, then
           that is the entire subfile. */
        if (!tags->next_ent)
          {
            int i = tags->position + 1;
            char last_char = the_file[i];

            while (i < file_size)
              {
                if ((the_file[i] == '\037') &&
                    ((last_char == '\n') ||
                     (last_char == '\014')))
                  break;
                else
                  last_char = the_file[i];
                i++;
              }
            file_bot = i;
            tags = tags->next_ent;
            goto write_region;
          }

        /* Otherwise, find the largest number of nodes that can fit in
           this subfile. */
        for (; tags; tags = tags->next_ent)
          {
            if (!tags->next_ent)
              {
                /* This entry is the last node.  Search forward for the end
                   of this node, and that is the end of this file. */
                int i = tags->position + 1;
                char last_char = the_file[i];

                while (i < file_size)
                  {
                    if ((the_file[i] == '\037') &&
                        ((last_char == '\n') ||
                         (last_char == '\014')))
                      break;
                    else
                      last_char = the_file[i];
                    i++;
                  }
                file_bot = i;

                if (file_bot < limit)
                  {
                    tags = tags->next_ent;
                    goto write_region;
                  }
                else
                  {
                    /* Here we want to write out everything before the last
                       node, and then write the last node out in a file
                       by itself. */
                    file_bot = tags->position;
                    goto write_region;
                  }
              }

            if (tags->next_ent->position > limit)
              {
                if (tags->position == file_top)
                  tags = tags->next_ent;

                file_bot = tags->position;

              write_region:
                {
                  int fd;
                  char *split_filename;

                  split_filename = (char *) xmalloc
                    (10 + strlen (root_pathname) + strlen (root_filename));
                  sprintf
                    (split_filename,
                     "%s%s-%d", root_pathname, root_filename, which_file);

                  fd = open
                    (split_filename, O_WRONLY | O_TRUNC | O_CREAT, 0666);

                  if ((fd < 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_filename);
                      if (fd != -1)
                        close (fd);
                      exit (FATAL);
                    }

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

                  sprintf (indirect_info, "%s-%d: %d\n",
                           root_filename, which_file, file_top);

                  free (split_filename);
                  indirect_info += strlen (indirect_info);
                  which_file++;
                  break;
                }
              }
          }
      }

    /* We have sucessfully created the subfiles.  Now write out the
       original again.  We must use `output_stream', or
       write_tag_table_indirect () won't know where to place the output. */
    output_stream = fopen (filename, "w");
    if (!output_stream)
      {
        perror (filename);
        exit (FATAL);
      }

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

      /* Inhibit newlines. */
      paragraph_is_open = 0;

      write_tag_table_indirect ();
      fclose (output_stream);
      free (the_header);
      free (the_file);
      return;
    }
  }
}

/* The strings here are followed in the message by `reference to...' in
   the `validate' routine.  */
char *
reftype_type_string (type)
     enum reftype type;
{
  switch (type)
    {
    case menu_reference:
      return ("Menu");
    case followed_reference:
      return ("Cross");
    default:
      return ("Internal-bad-reference-type");
    }
}

/* Remember this node name for later validation use.  This is used to
   remember menu references while reading the input file.  After the
   output file has been written, if validation is on, then we use the
   contents of `node_references' as a list of nodes to validate.  */
void
remember_node_reference (node, line, type)
     char *node;
     int line;
     enum reftype type;
{
  NODE_REF *temp = (NODE_REF *) xmalloc (sizeof (NODE_REF));

  temp->next = node_references;
  temp->node = xstrdup (node);
  temp->line_no = line;
  temp->section = current_section;
  temp->type = type;
  temp->containing_node = xstrdup (current_node ? current_node : "");
  temp->filename = node_filename;

  node_references = temp;
}

void
validate_other_references (ref_list)
     register NODE_REF *ref_list;
{
  char *old_input_filename = input_filename;

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

/* Find NODE in REF_LIST. */
NODE_REF *
find_node_reference (node, ref_list)
     char *node;
     register NODE_REF *ref_list;
{
  while (ref_list)
    {
      if (strcmp (node, ref_list->node) == 0)
        break;
      ref_list = ref_list->next;
    }
  return (ref_list);
}

void
free_node_references ()
{
  register NODE_REF *list, *temp;

  list = node_references;

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

  /* 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. */
#define menu_starter "* "
char *
glean_node_from_menu (remember_reference)
     int remember_reference;
{
  int i, orig_offset = input_text_offset;
  char *nodename;

  if (strncmp (&input_text[input_text_offset + 1],
               menu_starter,
               strlen (menu_starter)) != 0)
    return ((char *)NULL);
  else
    input_text_offset += strlen (menu_starter) + 1;

  get_until_in_line (0, ":", &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--;

  isolate_nodename (nodename);

save_node:
  input_text_offset = orig_offset;
  normalize_node_name (nodename);
  i = strlen (nodename);
  if (i && nodename[i - 1] == ':')
    nodename[i - 1] = 0;

  if (remember_reference)
    {
      remember_node_reference (nodename, line_number, menu_reference);
      free (nodename);
      return ((char *)NULL);
    }
  else
    return (nodename);
}

static void
isolate_nodename (nodename)
     char *nodename;
{
  register int i, c;
  int paren_seen, paren;

  if (!nodename)
    return;

  canon_white (nodename);
  paren_seen = paren = i = 0;

  if (*nodename == '.' || !*nodename)
    {
      *nodename = 0;
      return;
    }

  if (*nodename == '(')
    {
      paren++;
      paren_seen++;
      i++;
    }

  for (; (c = nodename[i]); i++)
    {
      if (paren)
        {
          if (c == '(')
            paren++;
          else if (c == ')')
            paren--;

          continue;
        }

      /* If the character following the close paren is a space, then this
         node has no more characters associated with it. */
      if (c == '\t' ||
          c == '\n' ||
          c == ','  ||
          ((paren_seen && nodename[i - 1] == ')') &&
           (c == ' ' || c == '.')) ||
          (c == '.' &&
           ((!nodename[i + 1] ||
             (cr_or_whitespace (nodename[i + 1])) ||
             (nodename[i + 1] == ')')))))
        break;
    }
  nodename[i] = 0;
}

void
cm_menu ()
{
  if (current_node == (char *)NULL)
    {
      warning (_("%cmenu seen before first node"), COMMAND_PREFIX);
      warning (_("creating `Top' node"));
      execute_string ("@node Top");
    }
  begin_insertion (menu);
}

void
cm_detailmenu ()
{
  if (current_node == (char *)NULL)
    {
      warning (_("%cmenu seen before first node"), COMMAND_PREFIX);
      warning (_("creating `Top' node"));
      execute_string ("@node Top");
    }
  begin_insertion (detailmenu);
}

/* **************************************************************** */
/*                                                                  */
/*                      Cross Reference Hacking                     */
/*                                                                  */
/* **************************************************************** */

/* Return next comma-delimited argument, but do not cross a close-brace
   boundary.  Clean up whitespace, too.  */
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. */
void
cm_xref (arg)
{
  if (arg == START)
    {
      char *arg1, *arg2, *arg3, *arg4, *arg5;

      arg1 = get_xref_token ();
      arg2 = get_xref_token ();
      arg3 = get_xref_token ();
      arg4 = get_xref_token ();
      arg5 = get_xref_token ();

      add_word_args ("%s", px_ref_flag ? "*note " : "*Note ");

      if (*arg5 || *arg4)
        {
          char *node_name;

          if (!*arg2)
            {
              if (*arg3)
                node_name = arg3;
              else
                node_name = arg1;
            }
          else
            node_name = arg2;

          execute_string ("%s: (%s)%s", node_name, arg4, arg1);
          /* Free all of the arguments found. */
          if (arg1) free (arg1);
          if (arg2) free (arg2);
          if (arg3) free (arg3);
          if (arg4) free (arg4);
          if (arg5) free (arg5);
          return;
        }
      else
        remember_node_reference (arg1, line_number, followed_reference);

      if (*arg3)
        {
          if (!*arg2)
            execute_string ("%s: %s", arg3, arg1);
          else
            execute_string ("%s: %s", arg2, arg1);
        }
      else
        {
          if (*arg2)
            execute_string ("%s: %s", arg2, arg1);
          else
            execute_string ("%s::", arg1);
        }

      /* Free all of the arguments found. */
      if (arg1) free (arg1);
      if (arg2) free (arg2);
      if (arg3) free (arg3);
      if (arg4) free (arg4);
      if (arg5) free (arg5);
    }
  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')
               {
                 line_error (
                          _("`.' or `,' must follow cross reference, not %c"), 
                             input_text[temp]);
                }
              break;
            }
        }
    }
}

void
cm_pxref (arg)
     int arg;
{
  if (arg == START)
    {
      px_ref_flag++;
      cm_xref (arg);
      px_ref_flag--;
    }
  else
    add_char ('.');
}

void
cm_inforef (arg)
     int arg;
{
  if (arg == START)
    {
      char *node = get_xref_token ();
      char *pname = get_xref_token ();
      char *file = get_xref_token ();

      if (*pname)
        execute_string ("*note %s: (%s)%s", pname, file, node);
      else
        execute_string ("*note (%s)%s::", file, node);

      free (node);
      free (pname);
      free (file);
    }
}

/* A URL reference.  */
void
cm_uref (arg, start_pos, end_pos)
     int arg, start_pos, end_pos;
{
  if (arg == END)
    {
      char *comma;
      char *arg = (char *) &output_paragraph[start_pos];

      output_paragraph[end_pos] = 0;
      output_column -= end_pos - start_pos;
      output_paragraph_offset = start_pos;

      arg = xstrdup (arg);
      comma = strchr (arg, ','); /* let's hope for no commas in the url  */
      if (comma)
        {
          *comma = 0;
          /* Ignore spaces at beginning of second arg.  */
          for (comma++; isspace (*comma); comma++)
            ;
          add_word (comma);
          add_char (' ');
          add_char ('(');
          add_word (arg);
          add_char (')');
        }
      else
        {
          extern int printing_index;
          
          if (!printing_index)
            add_char ('`');
            
          add_word (arg);
          
          if (!printing_index)
            add_char ('\'');
        }
      free (arg);
   }
}

/* An email reference.  */
void
cm_email (arg, start_pos, end_pos)
     int arg, start_pos, end_pos;
{
  if (arg == END)
    {
      char *comma;
      char *arg = (char *) &output_paragraph[start_pos];

      output_paragraph[end_pos] = 0;
      output_column -= end_pos - start_pos;
      output_paragraph_offset = start_pos;

      arg = xstrdup (arg);
      comma = strchr (arg, ',');
      if (comma)
        {
          *comma = 0;
          for (comma++; isspace (*comma); comma++)
            ;
          add_word (comma);
          add_char (' ');
        }
      add_char ('<');
      add_word (arg);
      add_char ('>');
      free (arg);
    }
}

/* An external image is a reference, kind of.  The parsing is (not
   coincidentally) similar, anyway.  */
void
cm_image (arg)
     int arg;
{
  if (arg == START)
    {
      char *name_arg = get_xref_token ();
      /* We don't yet care about any other args, but read them so they
         don't end up in the text.  */
      char *arg = get_xref_token ();
      if (arg) free (arg);
      arg = get_xref_token ();
      if (arg) free (arg);
      
      if (*name_arg)
        {
          /* Try to open foo.txt.  */
          FILE *image_file;
          char *name = xmalloc (strlen (name_arg) + 4);
          strcpy (name, name_arg);
          strcat (name, ".txt");
          image_file = fopen (name, "r");
          if (image_file)
            {
              int ch;
              int save_inhibit_indentation = inhibit_paragraph_indentation;
              int save_filling_enabled = filling_enabled;
              
              inhibit_paragraph_indentation = 1;
              filling_enabled = 0;
              last_char_was_newline = 0;
              
              /* Maybe we need to remove the final newline if the image
                 file is only one line to allow in-line images.  On the
                 other hand, they could just make the file without a
                 final newline.  */
              while ((ch = getc (image_file)) != EOF)
                add_char (ch);
              
              inhibit_paragraph_indentation = save_inhibit_indentation;
              filling_enabled = save_filling_enabled;
              
              if (fclose (image_file) != 0) {
                perror (name);
              }
            }
          else
            warning (_("@image file `%s' unreadable: %s"), name,
                     strerror (errno));
        }
      else
        line_error (_("@image missing filename argument"));

      if (name_arg) free (name_arg);
    }
}

/* **************************************************************** */
/*                                                                  */
/*                      Insertion Command Stubs                     */
/*                                                                  */
/* **************************************************************** */

void
cm_quotation ()
{
  begin_insertion (quotation);
}

void
cm_example ()
{
  begin_insertion (example);
}

void
cm_smallexample ()
{
  begin_insertion (smallexample);
}

void
cm_lisp ()
{
  begin_insertion (lisp);
}

void
cm_smalllisp ()
{
  begin_insertion (smalllisp);
}

/* @cartouche/@end cartouche draws box with rounded corners in
   TeX output.  Right now, just a no-op insertion. */
void
cm_cartouche ()
{
  begin_insertion (cartouche);
}

void
cm_format ()
{
  begin_insertion (format);
}

void
cm_display ()
{
  begin_insertion (display);
}

void
cm_direntry ()
{
  if (no_headers)
    command_name_condition ();
  else
    begin_insertion (direntry);
}

void
cm_itemize ()
{
  begin_insertion (itemize);
}

void
cm_enumerate ()
{
  do_enumeration (enumerate, "1");
}

/* Start an enumeration insertion of type TYPE.  If the user supplied
   no argument on the line, then use DEFAULT_STRING as the initial string. */
void
do_enumeration (type, default_string)
     int type;
     char *default_string;
{
  get_until_in_line (0, ".", &enumeration_arg);
  canon_white (enumeration_arg);

  if (!*enumeration_arg)
    {
      free (enumeration_arg);
      enumeration_arg = xstrdup (default_string);
    }

  if (!isdigit (*enumeration_arg) && !isletter (*enumeration_arg))
    {
      warning (_("%s requires letter or digit"), insertion_type_pname (type));

      switch (type)
        {
        case enumerate:
          default_string = "1";
          break;
        }
      enumeration_arg = xstrdup (default_string);
    }
  begin_insertion (type);
}

void
cm_table ()
{
  begin_insertion (table);
}

void
cm_multitable ()
{
  begin_insertion (multitable); /* @@ */
}

void
cm_ftable ()
{
  begin_insertion (ftable);
}

void
cm_vtable ()
{
  begin_insertion (vtable);
}

void
cm_group ()
{
  begin_insertion (group);
}

void
cm_ifinfo ()
{
  begin_insertion (ifinfo);
}

void
cm_ifnothtml ()
{
  begin_insertion (ifnothtml);
}

void
cm_ifnottex ()
{
  begin_insertion (ifnottex);
}

/* Begin an insertion where the lines are not filled or indented. */
void
cm_flushleft ()
{
  begin_insertion (flushleft);
}

/* Begin an insertion where the lines are not filled, and each line is
   forced to the right-hand side of the page. */
void
cm_flushright ()
{
  begin_insertion (flushright);
}

/* End existing insertion block. */
void
cm_end ()
{
  char *temp;
  enum insertion_type type;

  if (!insertion_level)
    {
      line_error (_("Unmatched `%c%s'"), COMMAND_PREFIX, command);
      return;
    }

  get_rest_of_line (&temp);

  if (temp[0] == 0)
    line_error (_("`%c%s' needs something after it"), COMMAND_PREFIX, 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);
}

/* **************************************************************** */
/*                                                                  */
/*                        Conditional Handling                      */
/*                                                                  */
/* **************************************************************** */

/* A structure which contains `defined' variables. */
typedef struct defines {
  struct defines *next;
  char *name;
  char *value;
} DEFINE;

/* The linked list of `set' defines. */
DEFINE *defines = (DEFINE *)NULL;

/* Add NAME to the list of `set' defines. */
void
set (name, value)
     char *name;
     char *value;
{
  DEFINE *temp;

  for (temp = defines; temp; temp = temp->next)
    if (strcmp (name, temp->name) == 0)
      {
        free (temp->value);
        temp->value = xstrdup (value);
        return;
      }

  temp = (DEFINE *)xmalloc (sizeof (DEFINE));
  temp->next = defines;
  temp->name = xstrdup (name);
  temp->value = xstrdup (value);
  defines = temp;
}

/* Remove NAME from the list of `set' defines. */
void
clear (name)
     char *name;
{
  register DEFINE *temp, *last;

  last = (DEFINE *)NULL;
  temp = defines;

  while (temp)
    {
      if (strcmp (temp->name, name) == 0)
        {
          if (last)
            last->next = temp->next;
          else
            defines = temp->next;

          free (temp->name);
          free (temp->value);
          free (temp);
          break;
        }
      last = temp;
      temp = temp->next;
    }
}

/* Return the value of NAME.  The return value is NULL if NAME is unset. */
char *
set_p (name)
     char *name;
{
  register DEFINE *temp;

  for (temp = defines; temp; temp = temp->next)
    if (strcmp (temp->name, name) == 0)
      return (temp->value);

  return ((char *)NULL);
}

/* Conditionally parse based on the current command name. */
void
command_name_condition ()
{
  char *discarder;

  discarder = (char *)xmalloc (8 + strlen (command));

  sprintf (discarder, "\n%cend %s", COMMAND_PREFIX, command);
  discard_until (discarder);
  discard_until ("\n");

  free (discarder);
}

/* Create a variable whose name appears as the first word on this line. */
void
cm_set ()
{
  handle_variable (SET);
}

/* Remove a variable whose name appears as the first word on this line. */
void
cm_clear ()
{
  handle_variable (CLEAR);
}

void
cm_ifset ()
{
  handle_variable (IFSET);
}

void
cm_ifclear ()
{
  handle_variable (IFCLEAR);
}

/* This command takes braces, but we parse the contents specially, so we
   don't use the standard brace popping code.

   The syntax @ifeq{arg1, arg2, texinfo-commands} performs texinfo-commands
   if ARG1 and ARG2 caselessly string compare to the same string, otherwise,
   it produces no output. */
void
cm_ifeq ()
{
  char **arglist;

  arglist = get_brace_args (0);

  if (arglist)
    {
      if (array_len (arglist) > 1)
        {
          if ((strcasecmp (arglist[0], arglist[1]) == 0) &&
              (arglist[2] != (char *)NULL))
            execute_string ("%s\n", arglist[2]);
        }

      free_array (arglist);
    }
}

void
cm_value (arg, start_pos, end_pos)
     int arg, start_pos, end_pos;
{
  if (arg == END)
    {
      char *name = (char *) &output_paragraph[start_pos];
      char *value;
      output_paragraph[end_pos] = 0;
      name = xstrdup (name);
      value = set_p (name);
      output_column -= end_pos - start_pos;
      output_paragraph_offset = start_pos;

      if (value)
        execute_string ("%s", value);
      else
        add_word_args (_("{No Value For \"%s\"}"), name);

      free (name);
    }
}

/* Set, clear, or conditionalize based on ACTION. */
void
handle_variable (action)
     int action;
{
  char *name;

  get_rest_of_line (&name);
  backup_input_pointer ();
  handle_variable_internal (action, name);
  free (name);
}

void
handle_variable_internal (action, name)
     int action;
     char *name;
{
  char *temp;
  int delimiter, additional_text_present = 0;

  /* Only the first word of NAME is a valid tag. */
  temp = name;
  delimiter = 0;
  while (*temp && (delimiter || !whitespace (*temp)))
    {
/* #if defined (SET_WITH_EQUAL) */
      if (*temp == '"' || *temp == '\'')
        {
          if (*temp == delimiter)
            delimiter = 0;
          else
            delimiter = *temp;
        }
/* #endif SET_WITH_EQUAL */
      temp++;
    }

  if (*temp)
    additional_text_present++;

  *temp = 0;

  if (!*name)
    line_error (_("%c%s requires a name"), COMMAND_PREFIX, command);
  else
    {
      switch (action)
        {
        case SET:
          {
            char *value;

#if defined (SET_WITH_EQUAL)
            /* Allow a value to be saved along with a variable.  The value is
               the text following an `=' sign in NAME, if any is present. */

            for (value = name; *value && *value != '='; value++);

            if (*value)
              *value++ = 0;

            if (*value == '"' || *value == '\'')
              {
                value++;
                value[strlen (value) - 1] = 0;
              }

#else /* !SET_WITH_EQUAL */
            /* The VALUE of NAME is the remainder of the line sans
               whitespace. */
            if (additional_text_present)
              {
                value = temp + 1;
                canon_white (value);
              }
            else
              value = "";
#endif /* !SET_WITH_VALUE */

            set (name, value);
          }
          break;

        case CLEAR:
          clear (name);
          break;

        case IFSET:
        case IFCLEAR:
          /* If IFSET and NAME is not set, or if IFCLEAR and NAME is set,
             read lines from the file until we reach a matching
             "@end CONDITION".  This means that we only take note of
             "@ifset/clear" and "@end" commands. */
          {
            char condition[8];
            int condition_len;
            int orig_line_number = line_number;

            if (action == IFSET)
              strcpy (condition, "ifset");
            else
              strcpy (condition, "ifclear");

            condition_len = strlen (condition);

          if ((action == IFSET && !set_p (name))
              || (action == IFCLEAR && set_p (name)))
            {
              int level = 0, done = 0;

              while (!done && input_text_offset < size_of_input_text)
                {
                  char *freeable_line, *line;

                  get_rest_of_line (&freeable_line);

                  for (line = freeable_line; whitespace (*line); line++);

                  if (*line == COMMAND_PREFIX &&
                      (strncmp (line + 1, condition, condition_len) == 0))
                    level++;
                  else if (strncmp (line, "@end", 4) == 0)
                    {
                      char *cname = line + 4;
                      char *temp;

                      while (*cname && whitespace (*cname))
                        cname++;
                      temp = cname;

                      while (*temp && !whitespace (*temp))
                        temp++;
                      *temp = 0;

                      if (strcmp (cname, condition) == 0)
                        {
                          if (!level)
                            {
                              done = 1;
                            }
                          else
                            level--;
                        }
                    }
                  free (freeable_line);
                }
              
              if (!done)
                {
                  int save = line_number;
                  line_number = orig_line_number;
                  line_error (_("Reached eof before matching @end %s"),
                              condition);
                  line_number = save;
                }
                
              /* We found the end of a false @ifset/ifclear.  If we are
                 in a menu, back up over the newline that ends the ifset,
                 since that newline may also begin the next menu entry. */
              break;
            }
          else
            {
              if (action == IFSET)
                begin_insertion (ifset);
              else
                begin_insertion (ifclear);
            }
          }
          break;
        }
    }
}

/* Execution of random text not in file. */

typedef struct {
  char *string;                 /* The string buffer. */
  int size;                     /* The size of the buffer. */
  int in_use;                   /* Nonzero means string currently in use. */
} EXECUTION_STRING;

static EXECUTION_STRING **execution_strings = (EXECUTION_STRING **)NULL;
static int execution_strings_index = 0;
static int execution_strings_slots = 0;

EXECUTION_STRING *
get_execution_string (initial_size)
     int initial_size;
{
  register int i = 0;
  EXECUTION_STRING *es = (EXECUTION_STRING *)NULL;

  if (execution_strings)
    {
      for (i = 0; i < execution_strings_index; i++)
        if (execution_strings[i] && (execution_strings[i]->in_use == 0))
          {
            es = execution_strings[i];
            break;
          }
    }

  if (!es)
    {
      if (execution_strings_index + 1 >= execution_strings_slots)
        {
          execution_strings = (EXECUTION_STRING **)xrealloc
            (execution_strings,
             (execution_strings_slots += 3) * sizeof (EXECUTION_STRING *));
          for (; i < execution_strings_slots; i++)
            execution_strings[i] = (EXECUTION_STRING *)NULL;
        }

      execution_strings[execution_strings_index] =
        (EXECUTION_STRING *)xmalloc (sizeof (EXECUTION_STRING));
      es = execution_strings[execution_strings_index];
      execution_strings_index++;

      es->size = 0;
      es->string = (char *)NULL;
      es->in_use = 0;
    }

  if (initial_size > es->size)
    {
      es->string = (char *) xrealloc (es->string, initial_size);
      es->size = initial_size;
    }
  return (es);
}

/* Execute the string produced by formatting the ARGs with FORMAT.  This
   is like submitting a new file with @include. */
void
#if defined (VA_FPRINTF) && __STDC__
execute_string (char *format, ...)
#else
execute_string (format, va_alist)
    char *format;
    va_dcl
#endif
{
  EXECUTION_STRING *es;
  char *temp_string;
#ifdef VA_FPRINTF
  va_list ap;
#endif

  es = get_execution_string (4000);
  temp_string = es->string;
  es->in_use = 1;

  VA_START (ap, format);
#ifdef VA_SPRINTF
  VA_SPRINTF (temp_string, format, ap);
#else
  sprintf (temp_string, format, a1, a2, a3, a4, a5, a6, a7, a8);
#endif /* not VA_SPRINTF */
  va_end (ap);

  pushfile ();
  input_text_offset = 0;
  input_text = temp_string;
  input_filename = xstrdup (input_filename);
  size_of_input_text = strlen (temp_string);

  executing_string++;
  reader_loop ();
  free (input_filename);

  popfile ();
  executing_string--;
  es->in_use = 0;
}


/* Return what would be output for STR, i.e., expand Texinfo commands.
   If IMPLICIT_CODE is set, expand @code{STR}.  */

char *
expansion (str, implicit_code)
    char *str;
    int implicit_code;
{
  int length;
  char *result;

  /* Inhibit any real output.  */
  int start = output_paragraph_offset;
  int saved_paragraph_is_open = paragraph_is_open;

  inhibit_output_flushing ();
  paragraph_is_open = 1;
  execute_string (implicit_code ? "@code{%s}" : "%s", str);
  uninhibit_output_flushing ();

  /* Copy the expansion from the buffer.  */
  length = output_paragraph_offset - start;
  result = xmalloc (1 + length);
  memcpy (result, (char *) (output_paragraph + start), length);
  result[length] = 0;
  
  /* Pretend it never happened.  */
  output_paragraph_offset = start;
  paragraph_is_open = saved_paragraph_is_open;

  return result;
}

/* @itemx, @item. */

static int itemx_flag = 0;

void
cm_itemx ()
{
  itemx_flag++;
  cm_item ();
  itemx_flag--;
}

void
cm_item ()
{
  char *rest_of_line, *item_func;

  /* Can only hack "@item" while inside of an insertion. */
  if (insertion_level)
    {
      INSERTION_ELT *stack = insertion_stack;
      int original_input_text_offset;

      skip_whitespace ();
      original_input_text_offset = input_text_offset;

      get_rest_of_line (&rest_of_line);
      item_func = current_item_function ();

      /* Okay, do the right thing depending on which insertion function
         is active. */

    switch_top:
      switch (stack->insertion)
        {
        case multitable:
          multitable_item ();
          /* Ultra special hack.  It appears that some people incorrectly
             place text directly after the @item, instead of on a new line
             by itself.  This happens to work in TeX, so I make it work
             here. */
          if (*rest_of_line)
            {
              line_number--;
              input_text_offset = original_input_text_offset;
            }
          break;

        case ifinfo:
        case ifset:
        case ifclear:
        case cartouche:
          stack = stack->next;
          if (!stack)
            goto no_insertion;
          else
            goto switch_top;
          break;

        case menu:
        case quotation:
        case example:
        case smallexample:
        case lisp:
        case format:
        case display:
        case group:
          line_error (_("The `%c%s' command is meaningless within a `@%s' block"),
                      COMMAND_PREFIX, command,
                      insertion_type_pname (current_insertion_type ()));
          break;

        case itemize:
        case enumerate:
          if (itemx_flag)
            {
              line_error (_("%citemx is not meaningful inside of a `%s' block"),
                          COMMAND_PREFIX,
                          insertion_type_pname (current_insertion_type ()));
            }
          else
            {
              start_paragraph ();
              kill_self_indent (-1);
              filling_enabled = indented_fill = 1;

              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 == COMMAND_PREFIX)
                        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
                enumerate_item ();

              /* Special hack.  This makes `close_paragraph' a no-op until
                 `start_paragraph' has been called. */
              must_start_paragraph = 1;

              /* Handle text directly after the @item.  */
              if (*rest_of_line)
                {
                  line_number--;
                  input_text_offset = original_input_text_offset;
                }
            }
          break;

        case table:
        case ftable:
        case vtable:
          {
            /* We need this to determine if we have two @item's in a row
               (see test just below).  */
            static int last_item_output_position = 0;
            
            /* Get rid of extra characters. */
            kill_self_indent (-1);

            /* If we have one @item followed directly by another @item,
               we need to insert a blank line.  This is not true for
               @itemx, though.  */
            if (!itemx_flag && last_item_output_position == output_position)
              insert ('\n');
              
            /* `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 ();

#if defined (INDENT_PARAGRAPHS_IN_TABLE)
            /* Indent on a new line, but back up one indentation level. */
            {
              int save = inhibit_paragraph_indentation;
              inhibit_paragraph_indentation = 1;
              /* At this point, inserting any non-whitespace character will
                 force the existing indentation to be output. */
              add_char ('i');
              inhibit_paragraph_indentation = save;
            }
#else /* !INDENT_PARAGRAPHS_IN_TABLE */
            add_char ('i');
#endif /* !INDENT_PARAGRAPHS_IN_TABLE */

            output_paragraph_offset--;
            kill_self_indent (default_indentation_increment + 1);

            /* Add item's argument to the line. */
            filling_enabled = 0;
            if (item_func && *item_func)
              execute_string ("%s{%s}", item_func, rest_of_line);
            else
              execute_string ("%s", rest_of_line);

            if (current_insertion_type () == ftable)
              execute_string ("%cfindex %s\n", COMMAND_PREFIX, rest_of_line);
            else if (current_insertion_type () == vtable)
              execute_string ("%cvindex %s\n", COMMAND_PREFIX, 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 = 1;
            last_item_output_position = output_position;
          }
        }
      free (rest_of_line);
    }
  else
    {
    no_insertion:
      line_error (_("%c%s found outside of an insertion block"),
                  COMMAND_PREFIX, command);
    }
}

/* **************************************************************** */
/*                                                                  */
/*                      Defun and Friends                           */
/*                                                                  */
/* **************************************************************** */

#define DEFUN_SELF_DELIMITING(c)                                        \
  (((c) == '(')                                                         \
   || ((c) == ')')                                                      \
   || ((c) == '[')                                                      \
   || ((c) == ']'))

struct token_accumulator
{
  unsigned int length;
  unsigned int index;
  char **tokens;
};

void
initialize_token_accumulator (accumulator)
     struct token_accumulator *accumulator;
{
  (accumulator->length) = 0;
  (accumulator->index) = 0;
  (accumulator->tokens) = NULL;
}

void
accumulate_token (accumulator, token)
     struct token_accumulator *accumulator;
     char *token;
{
  if ((accumulator->index) >= (accumulator->length))
    {
      (accumulator->length) += 10;
      (accumulator->tokens) = (char **) xrealloc
        (accumulator->tokens, (accumulator->length * sizeof (char *)));
    }
  accumulator->tokens[accumulator->index] = token;
  accumulator->index += 1;
}

char *
copy_substring (start, end)
     char *start;
     char *end;
{
  char *result, *scan, *scan_result;

  result = (char *) xmalloc ((end - start) + 1);
  scan_result = result;
  scan = start;

  while (scan < end)
    *scan_result++ = *scan++;

  *scan_result = 0;
  return (result);
}

/* Given `string' pointing at an open brace, skip forward and return a
   pointer to just past the matching close brace. */
int
scan_group_in_string (string_pointer)
     char **string_pointer;
{
  register int c;
  register char *scan_string;
  register unsigned int level = 1;

  scan_string = (*string_pointer) + 1;

  while (1)
    {
      if (level == 0)
        {
          (*string_pointer) = scan_string;
          return (1);
        }
      c = (*scan_string++);
      if (c == 0)
        {
          /* Tweak line_number to compensate for fact that
             we gobbled the whole line before coming here. */
          line_number -= 1;
          line_error (_("Missing `}' in %cdef arg"), COMMAND_PREFIX);
          line_number += 1;
          (*string_pointer) = (scan_string - 1);
          return (0);
        }
      if (c == '{')
        level += 1;
      if (c == '}')
        level -= 1;
    }
}

/* Return a list of tokens from the contents of `string'.
   Commands and brace-delimited groups count as single tokens.
   Contiguous whitespace characters are converted to a token
   consisting of a single space. */
char **
args_from_string (string)
     char *string;
{
  struct token_accumulator accumulator;
  register char *scan_string = string;
  char *token_start, *token_end;

  initialize_token_accumulator (&accumulator);

  while ((*scan_string) != 0)
    {
      /* Replace arbitrary whitespace by a single space. */
      if (whitespace (*scan_string))
        {
          scan_string += 1;
          while (whitespace (*scan_string))
            scan_string += 1;
          accumulate_token ((&accumulator), (xstrdup (" ")));
          continue;
        }

      /* Commands count as single tokens. */
      if ((*scan_string) == COMMAND_PREFIX)
        {
          token_start = scan_string;
          scan_string += 1;
          if (self_delimiting (*scan_string))
            scan_string += 1;
          else
            {
              register int c;
              while (1)
                {
                  c = *scan_string++;

                  if ((c == 0) || (c == '{') || (whitespace (c)))
                    {
                      scan_string -= 1;
                      break;
                    }
                }

              if (*scan_string == '{')
                {
                  char *s = scan_string;
                  (void) scan_group_in_string (&s);
                  scan_string = s;
                }
            }
          token_end = scan_string;
        }

      /* Parentheses and brackets are self-delimiting. */
      else if (DEFUN_SELF_DELIMITING (*scan_string))
        {
          token_start = scan_string;
          scan_string += 1;
          token_end = scan_string;
        }

      /* Open brace introduces a group that is a single token. */
      else if (*scan_string == '{')
        {
          char *s = scan_string;
          int balanced = scan_group_in_string (&s);

          token_start = scan_string + 1;
          scan_string = s;
          token_end = balanced ? (scan_string - 1) : scan_string;
        }

      /* Otherwise a token is delimited by whitespace, parentheses,
         brackets, or braces.  A token is also ended by a command. */
      else
        {
          token_start = scan_string;

          while (1)
            {
              register int c;

              c = *scan_string++;

              /* Do not back up if we're looking at a }; since the only
                 valid }'s are those matched with {'s, we want to give
                 an error.  If we back up, we go into an infinite loop.  */
              if (!c || whitespace (c) || DEFUN_SELF_DELIMITING (c)
                  || c == '{')
                {
                  scan_string--;
                  break;
                }

              /* If we encounter a command embedded within a token,
                 then end the token. */
              if (c == COMMAND_PREFIX)
                {
                  scan_string--;
                  break;
                }
            }
          token_end = scan_string;
        }

      accumulate_token
        (&accumulator, copy_substring (token_start, token_end));
    }
  accumulate_token (&accumulator, NULL);
  return (accumulator.tokens);
}

void
process_defun_args (defun_args, auto_var_p)
     char **defun_args;
     int auto_var_p;
{
  int pending_space = 0;

  while (1)
    {
      char *defun_arg = *defun_args++;

      if (defun_arg == NULL)
        break;

      if (defun_arg[0] == ' ')
        {
          pending_space = 1;
          continue;
        }

      if (pending_space)
        {
          add_char (' ');
          pending_space = 0;
        }

      if (DEFUN_SELF_DELIMITING (defun_arg[0]))
        add_char (defun_arg[0]);
      else if (defun_arg[0] == '&')
        add_word (defun_arg);
      else if (defun_arg[0] == COMMAND_PREFIX)
        execute_string ("%s", defun_arg);
      else if (auto_var_p)
        execute_string ("%cvar{%s}", COMMAND_PREFIX, defun_arg);
      else
        add_word (defun_arg);
    }
}

char *
next_nonwhite_defun_arg (arg_pointer)
     char ***arg_pointer;
{
  char **scan = (*arg_pointer);
  char *arg = (*scan++);

  if ((arg != 0) && (*arg == ' '))
    arg = *scan++;

  if (arg == 0)
    scan -= 1;

  *arg_pointer = scan;

  return ((arg == 0) ? "" : arg);
}

/* Make the defun type insertion.
   TYPE says which insertion this is.
   X_P, if nonzero, says not to start a new insertion. */
void
defun_internal (type, x_p)
     enum insertion_type type;
     int x_p;
{
  enum insertion_type base_type;
  char **defun_args, **scan_args;
  char *category, *defined_name, *type_name, *type_name2;

  {
    char *line;
    get_rest_of_line (&line);
    defun_args = (args_from_string (line));
    free (line);
  }

  scan_args = defun_args;

  switch (type)
    {
    case defun:
      category = _("Function");
      base_type = deffn;
      break;
    case defmac:
      category = _("Macro");
      base_type = deffn;
      break;
    case defspec:
      category = _("Special Form");
      base_type = deffn;
      break;
    case defvar:
      category = _("Variable");
      base_type = defvr;
      break;
    case defopt:
      category = _("User Option");
      base_type = defvr;
      break;
    case deftypefun:
      category = _("Function");
      base_type = deftypefn;
      break;
    case deftypevar:
      category = _("Variable");
      base_type = deftypevr;
      break;
    case defivar:
      category = _("Instance Variable");
      base_type = defcv;
      break;
    case defmethod:
      category = _("Method");
      base_type = defop;
      break;
    case deftypemethod:
      category = _("Method");
      base_type = deftypemethod;
      break;
    default:
      category = next_nonwhite_defun_arg (&scan_args);
      base_type = type;
      break;
    }

  if ((base_type == deftypefn)
      || (base_type == deftypevr)
      || (base_type == defcv)
      || (base_type == defop)
      || (base_type == deftypemethod))
    type_name = next_nonwhite_defun_arg (&scan_args);

  if (base_type == deftypemethod)
    type_name2 = next_nonwhite_defun_arg (&scan_args);

  defined_name = next_nonwhite_defun_arg (&scan_args);

  /* This hack exists solely for the purposes of formatting the texinfo
     manual.  I couldn't think of a better way.  The token might be
     a simple @@ followed immediately by more text.  If this is the case,
     then the next defun arg is part of this one, and we should concatenate
     them. */
  if (*scan_args && **scan_args && !whitespace (**scan_args) &&
      (strcmp (defined_name, "@@") == 0))
    {
      char *tem = (char *)xmalloc (3 + strlen (scan_args[0]));

      sprintf (tem, "@@%s", scan_args[0]);

      free (scan_args[0]);
      scan_args[0] = tem;
      scan_args++;
      defined_name = tem;
    }

  if (!x_p)
    begin_insertion (type);

  /* Write the definition header line.
     This should start at the normal indentation.  */
  current_indent -= default_indentation_increment;
  start_paragraph ();

  switch (base_type)
    {
    case deffn:
    case defvr:
    case deftp:
      execute_string (" -- %s: %s", category, defined_name);
      break;
    case deftypefn:
    case deftypevr:
      execute_string (" -- %s: %s %s", category, type_name, defined_name);
      break;
    case defcv:
      execute_string (" -- %s of %s: %s", category, type_name, defined_name);
      break;
    case defop:
      execute_string (" -- %s on %s: %s", category, type_name, defined_name);
      break;
    case deftypemethod:
      execute_string (" -- %s on %s: %s %s", category, type_name, type_name2,
                      defined_name);
      break;
    }
  current_indent += default_indentation_increment;

  /* Now process the function arguments, if any.
     If these carry onto the next line, they should be indented by two
     increments to distinguish them from the body of the definition,
     which is indented by one increment.  */
  current_indent += default_indentation_increment;

  switch (base_type)
    {
    case deffn:
    case defop:
      process_defun_args (scan_args, 1);
      break;

      /* Through Makeinfo 1.67 we processed remaining args only for deftp,
         deftypefn, and deftypemethod.  But the libc manual, for example,
         needs to say:
            @deftypevar {char *} tzname[2]
         And simply allowing the extra text seems far simpler than trying
         to invent yet more defn commands.  In any case, we should either
         output it or give an error, not silently ignore it.  */
    default:
      process_defun_args (scan_args, 0);
      break;
    }
  current_indent -= default_indentation_increment;
  close_single_paragraph ();

  /* Make an entry in the appropriate index. */
  switch (base_type)
    {
    case deffn:
    case deftypefn:
      execute_string ("%cfindex %s\n", COMMAND_PREFIX, defined_name);
      break;
    case defvr:
    case deftypevr:
    case defcv:
      execute_string ("%cvindex %s\n", COMMAND_PREFIX, defined_name);
      break;
    case defop:
    case deftypemethod:
      execute_string ("%cfindex %s on %s\n",
		      COMMAND_PREFIX, defined_name, type_name);
      break;
    case deftp:
      execute_string ("%ctindex %s\n", COMMAND_PREFIX, defined_name);
      break;
    }

  /* Deallocate the token list. */
  scan_args = defun_args;
  while (1)
    {
      char * arg = (*scan_args++);
      if (arg == NULL)
        break;
      free (arg);
    }
  free (defun_args);
}

/* Add an entry for a function, macro, special form, variable, or option.
   If the name of the calling command ends in `x', then this is an extra
   entry included in the body of an insertion of the same type. */
void
cm_defun ()
{
  int x_p;
  enum insertion_type type;
  char *temp = xstrdup (command);

  x_p = (command[strlen (command) - 1] == 'x');

  if (x_p)
    temp[strlen (temp) - 1] = 0;

  type = find_type_from_name (temp);
  free (temp);

  /* If we are adding to an already existing insertion, then make sure
     that we are already in an insertion of type TYPE. */
  if (x_p &&
      (!insertion_level || insertion_stack->insertion != type))
    {
      line_error (_("Must be in a `%s' insertion in order to use `%s'x"),
                  command, command);
      discard_until ("\n");
      return;
    }

  defun_internal (type, x_p);
}

/* **************************************************************** */
/*                                                                  */
/*                      Other Random Commands                       */
/*                                                                  */
/* **************************************************************** */

/* This says to inhibit the indentation of the next paragraph, but
   not of following paragraphs.  */
void
cm_noindent ()
{
  if (!inhibit_paragraph_indentation)
    inhibit_paragraph_indentation = -1;
}

/* 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. */
void
cm_setfilename ()
{
  char *filename;
  get_rest_of_line (&filename);
  /* warning ("`@%s %s' encountered and ignored", command, filename); */
  free (filename);
}

void
cm_ignore_line ()
{
  discard_until ("\n");
}

/* @br can be immediately followed by `{}', so we have to read those here.
   It should simply close the paragraph. */
void
cm_br ()
{
  if (looking_at ("{}"))
    input_text_offset += 2;

  if (curchar () == '\n')
    {
      input_text_offset++;
      line_number++;
    }

  close_paragraph ();
}

 /* Insert the number of blank lines passed as argument. */
void
cm_sp ()
{
  int lines;
  char *line;

  get_rest_of_line (&line);

  if (sscanf (line, "%d", &lines) != 1)
    {
      line_error (_("%csp requires a positive numeric argument"), COMMAND_PREFIX);
    }
  else
    {
      if (lines < 0)
        lines = 0;

      while (lines--)
        add_char ('\n');
    }
  free (line);
}

/* @dircategory LINE  outputs  INFO-DIR-SECTION LINE,
   but not if --no-headers.  */

void
cm_dircategory ()
{
  char *line;

  get_rest_of_line (&line);;

  if (!no_headers)
    {
      insert_string ("INFO-DIR-SECTION ");
      insert_string (line);
      insert ('\n');
    }

  free (line);
}

/* Start a new line with just this text on it.
   Then center the line of text.
   This always ends the current paragraph. */
void
cm_center ()
{
  register int i, start, length;
  int fudge_factor = 1;
  unsigned char *line;

  close_paragraph ();
  filling_enabled = indented_fill = 0;
  cm_noindent ();
  start = output_paragraph_offset;
  inhibit_output_flushing ();
  get_rest_of_line ((char **)&line);
  execute_string ("%s", (char *)line);
  free (line);
  uninhibit_output_flushing ();

  i = output_paragraph_offset - 1;
  while (i > (start - 1) && output_paragraph[i] == '\n')
        i--;

  output_paragraph_offset = ++i;
  length = output_paragraph_offset - start;

  if (length < (fill_column - fudge_factor))
    {
      line = (unsigned char *)xmalloc (1 + length);
      memcpy (line, (char *)(output_paragraph + start), length);

      i = (fill_column - fudge_factor - length) / 2;
      output_paragraph_offset = start;

      while (i--)
        insert (' ');

      for (i = 0; i < length; i++)
        insert (line[i]);

      free (line);
    }

  insert ('\n');
  close_paragraph ();
  filling_enabled = 1;
}

/* Show what an expression returns. */
void
cm_result (arg)
     int arg;
{
  if (arg == END)
    add_word ("=>");
}

/* What an expression expands to. */
void
cm_expansion (arg)
     int arg;
{
  if (arg == END)
    add_word ("==>");
}

/* Indicates two expressions are equivalent. */
void
cm_equiv (arg)
     int arg;
{
  if (arg == END)
    add_word ("==");
}

/* What an expression may print. */
void
cm_print (arg)
     int arg;
{
  if (arg == END)
    add_word ("-|");
}

/* An error signaled. */
void
cm_error (arg)
     int arg;
{
  if (arg == END)
    add_word ("error-->");
}

/* The location of point in an example of a buffer. */
void
cm_point (arg)
     int arg;
{
  if (arg == END)
    add_word ("-!-");
}

/* Start a new line with just this text on it.
   The text is outdented one level if possible. */
void
cm_exdent ()
{
  char *line;
  int i = current_indent;

  if (current_indent)
    current_indent -= default_indentation_increment;

  get_rest_of_line (&line);
  close_single_paragraph ();
  execute_string ("%s", line);
  current_indent = i;
  free (line);
  close_single_paragraph ();
}


/* Remember this file, and move onto the next. */
void
cm_include ()
{
  char *filename;

#if defined (HAVE_MACROS)
  if (macro_expansion_output_stream && !executing_string)
    me_append_before_this_command ();
#endif /* HAVE_MACROS */

  close_paragraph ();
  get_rest_of_line (&filename);

#if defined (HAVE_MACROS)
  if (macro_expansion_output_stream && !executing_string)
    remember_itext (input_text, input_text_offset);
#endif /* HAVE_MACROS */

  pushfile ();

  /* In verbose mode we print info about including another file. */
  if (verbose_mode)
    {
      register int i = 0;
      register FSTACK *stack = filestack;

      for (i = 0, stack = filestack; stack; stack = stack->next, i++);

      i *= 2;

      printf ("%*s", i, "");
      printf ("%c%s %s\n", COMMAND_PREFIX, command, filename);
      fflush (stdout);
    }

  if (!find_and_load (filename))
    {
      extern int errno;

      popfile ();
      line_number--;

      /* Cannot "@include foo", in line 5 of "/wh/bar". */
      line_error ("%c%s %s: %s", COMMAND_PREFIX, command, filename,
                  strerror (errno));

      free (filename);
      return;
    }
  else
    {
#if defined (HAVE_MACROS)
      if (macro_expansion_output_stream && !executing_string)
        remember_itext (input_text, input_text_offset);
#endif /* HAVE_MACROS */
      reader_loop ();
    }
  free (filename);
  popfile ();
}

/* The other side of a malformed expression. */
void
misplaced_brace ()
{
  line_error (_("Misplaced %c"), '}');
}

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

/* Set the paragraph indentation variable to the value specified in STRING.
   Values can be:
     `asis': Don't change existing indentation.
     `none': Remove existing indentation.
        NUM: Indent NUM spaces at the starts of paragraphs.
             If NUM is zero, we assume `none'.
   Returns 0 if successful, or nonzero if STRING isn't one of the above. */
int
set_paragraph_indent (string)
     char *string;
{
  if (strcmp (string, "asis") == 0 || strcmp (string, _("asis")) == 0)
    paragraph_start_indent = 0;
  else if (strcmp (string, "none") == 0 || strcmp (string, _("none")) == 0)
    paragraph_start_indent = -1;
  else
    {
      if (sscanf (string, "%d", &paragraph_start_indent) != 1)
        return (-1);
      else
        {
          if (paragraph_start_indent == 0)
            paragraph_start_indent = -1;
        }
    }
  return (0);
}

void
cm_paragraphindent ()
{
  char *arg;

  get_rest_of_line (&arg);
  if (set_paragraph_indent (arg) != 0)
    line_error (_("Bad argument to %c%s"), COMMAND_PREFIX, command);

  free (arg);
}

/* **************************************************************** */
/*                                                                  */
/*                      Indexing Stuff                              */
/*                                                                  */
/* **************************************************************** */


/* An index element... */
typedef struct index_elt
{
  struct index_elt *next;
  char *entry;                  /* The index entry itself. */
  char *node;                   /* The node from whence it came. */
  int code;                     /* Nonzero means add `@code{...}' when
                                   printing this element. */
  int defining_line;            /* Line number where this entry was written. */
  char *defining_file;          /* Source file for defining_line. */
} INDEX_ELT;

/* A list of short-names for each index.

   There are two indices into the the_indices array.

   * read_index is the index that points to the list of index
     entries that we will find if we ask for the list of entries for
     this name.

   * write_index is the index that points to the list of index entries
     that we will add new entries to.

   Initially, read_index and write index are the same, but the
   @syncodeindex and @synindex commands can change the list we add
   entries to.

   For example, after the commands

     @cindex foo
     @defindex ii
     @synindex cp ii
     @cindex bar

   the cp index will contain the entry `foo', and the new ii
   index will contain the entry `bar'.  This is consistent with the
   way texinfo.tex handles the same situation.

   In addition, for each index, it is remembered whether that index is
   a code index or not.  Code indices have @code{} inserted around the
   first word when they are printed with printindex. */
typedef struct
{
  char *name;
  int read_index;   /* index entries for `name' */
  int write_index;  /* store index entries here, @synindex can change it */
  int code;
} INDEX_ALIST;

INDEX_ALIST **name_index_alist = (INDEX_ALIST **) NULL;

/* An array of pointers.  Each one is for a different index.  The
   "synindex" command changes which array slot is pointed to by a
   given "index". */
INDEX_ELT **the_indices = (INDEX_ELT **) NULL;

/* The number of defined indices. */
int defined_indices = 0;

void
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);
      if (name_index_alist[i])
        { /* Suppose we're called with two input files, and the first
             does a @synindex pg cp.  Then, when we get here to start
             the second file, the "pg" element won't get freed by
             undefindex (because it's pointing to "cp").  So free it
             here; otherwise, when we try to define the pg index again
             just below, it will still point to cp.  */
          free (name_index_alist[i]->name);
          free (name_index_alist[i]);
          name_index_alist[i] = (INDEX_ALIST *) NULL;
        }
    }

  /* Add the default indices. */
  top_defindex ("cp", 0);           /* cp is the only non-code index.  */
  top_defindex ("fn", 1);
  top_defindex ("ky", 1);
  top_defindex ("pg", 1);
  top_defindex ("tp", 1);
  top_defindex ("vr", 1);
}

/* Find which element in the known list of indices has this name.
   Returns -1 if NAME isn't found. */
int
find_index_offset (name)
     char *name;
{
  register int i;
  for (i = 0; i < defined_indices; i++)
    if (name_index_alist[i] &&
        strcmp (name, name_index_alist[i]->name) == 0)
      return (i);
  return (-1);
}

/* Return a pointer to the entry of (name . index) for this name.
   Return NULL if the index doesn't exist. */
INDEX_ALIST *
find_index (name)
     char *name;
{
  int offset = find_index_offset (name);
  if (offset > -1)
    return (name_index_alist[offset]);
  else
    return ((INDEX_ALIST *) NULL);
}

/* Given an index name, return the offset in the_indices of this index,
   or -1 if there is no such index. */
int
translate_index (name)
     char *name;
{
  INDEX_ALIST *which = find_index (name);

  if (which)
    return (which->read_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... */
void
free_index (index)
     INDEX_ELT *index;
{
  INDEX_ELT *temp;

  while ((temp = index) != (INDEX_ELT *) NULL)
    {
      free (temp->entry);
      /* Do not free the node, because we already freed the tag table,
         which freed all the node names.  */
      /* free (temp->node); */
      index = index->next;
      free (temp);
    }
}

/* Flush an index by name.  This will delete the list of entries that
   would be written by a @printindex command for this index. */
void
undefindex (name)
     char *name;
{
  int i;
  int which = find_index_offset (name);

  /* The index might have already been freed if this was the target of
     an @synindex.  */
  if (which < 0 || !name_index_alist[which])
    return;

  i = name_index_alist[which]->read_index;

  free_index (the_indices[i]);
  the_indices[i] = (INDEX_ELT *) NULL;

  free (name_index_alist[which]->name);
  free (name_index_alist[which]);
  name_index_alist[which] = (INDEX_ALIST *) NULL;
}

/* Define an index known as NAME.  We assign the slot number.
   CODE if Nonzero says to make this a code index. */
void
defindex (name, code)
     char *name;
     int code;
{
  register int i, slot;

  /* If it already exists, flush it. */
  undefindex (name);

  /* Try to find an empty slot. */
  slot = -1;
  for (i = 0; i < defined_indices; i++)
    if (!name_index_alist[i])
      {
        slot = i;
        break;
      }

  if (slot < 0)
    {
      /* No such luck.  Make space for another index. */
      slot = defined_indices;
      defined_indices++;

      name_index_alist = (INDEX_ALIST **)
        xrealloc ((char *)name_index_alist,
                  (1 + defined_indices) * sizeof (INDEX_ALIST *));
      the_indices = (INDEX_ELT **)
        xrealloc ((char *)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 = xstrdup (name);
  name_index_alist[slot]->read_index = slot;
  name_index_alist[slot]->write_index = slot;
  name_index_alist[slot]->code = code;

  the_indices[slot] = (INDEX_ELT *) NULL;
}

/* Add the arguments to the current index command to the index NAME. */
void
index_add_arg (name)
     char *name;
{
  int which;
  char *index_entry;
  INDEX_ALIST *tem;

  tem = find_index (name);

  which = tem ? tem->write_index : -1;

#if defined (HAVE_MACROS)
  if (macro_expansion_output_stream && !executing_string)
    append_to_expansion_output (input_text_offset + 1);
#endif /* HAVE_MACROS */

  get_rest_of_line (&index_entry);
  ignore_blank_line ();

#if defined (HAVE_MACROS)
  if (macro_expansion_output_stream && !executing_string)
    {
      int op_orig;

      remember_itext (input_text, input_text_offset);
      op_orig = output_paragraph_offset;
      me_execute_string (index_entry);
      me_execute_string ("\n");
      output_paragraph_offset = op_orig;
    }
#endif /* HAVE_MACROS */

  if (which < 0)
    {
      line_error (_("Unknown index `%s'"), name);
      free (index_entry);
    }
  else
    {
      INDEX_ELT *new = (INDEX_ELT *) xmalloc (sizeof (INDEX_ELT));
      new->next = the_indices[which];
      new->entry = index_entry;
      new->node = current_node;
      new->code = tem->code;
      new->defining_line = line_number - 1;
      new->defining_file = input_filename;
      the_indices[which] = new;
    }
}

#define INDEX_COMMAND_SUFFIX "index"

/* The function which user defined index commands call. */
void
gen_index ()
{
  char *name = xstrdup (command);
  if (strlen (name) >= strlen ("index"))
    name[strlen (name) - strlen ("index")] = 0;
  index_add_arg (name);
  free (name);
}

void
top_defindex (name, code)
     char *name;
     int code;
{
  char *temp;

  temp = (char *) xmalloc (1 + strlen (name) + strlen ("index"));
  sprintf (temp, "%sindex", name);
  define_user_command (temp, gen_index, 0);
  defindex (name, code);
  free (temp);
}

/* Define a new index command.  Arg is name of index. */
void
cm_defindex ()
{
  gen_defindex (0);
}

void
cm_defcodeindex ()
{
  gen_defindex (1);
}

void
gen_defindex (code)
     int code;
{
  char *name;
  get_rest_of_line (&name);

  if (find_index (name))
    {
      line_error (_("Index `%s' already exists"), name);
      free (name);
      return;
    }
  else
    {
      char *temp = (char *) alloca (1 + strlen (name) + strlen ("index"));
      sprintf (temp, "%sindex", name);
      define_user_command (temp, gen_index, 0);
      defindex (name, code);
      free (name);
    }
}

/* 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. */
void
cm_synindex ()
{
  int source, target;
  char *abbrev1, *abbrev2;

  skip_whitespace ();
  get_until_in_line (0, " ", &abbrev1);
  target = find_index_offset (abbrev1);
  skip_whitespace ();
  get_until_in_line (0, " ", &abbrev2);
  source = find_index_offset (abbrev2);
  if (source < 0 || target < 0)
    {
      line_error (_("Unknown index `%s' and/or `%s' in @synindex"),
                  abbrev1, abbrev2);
    }
  else
    {
      name_index_alist[target]->write_index
        = name_index_alist[source]->write_index;
    }
  
  free (abbrev1);
  free (abbrev2);
}

void
cm_pindex ()                    /* Pinhead index. */
{
  index_add_arg ("pg");
}

void
cm_vindex ()                    /* Variable index. */
{
  index_add_arg ("vr");
}

void
cm_kindex ()                    /* Key index. */
{
  index_add_arg ("ky");
}

void
cm_cindex ()                    /* Concept index. */
{
  index_add_arg ("cp");
}

void
cm_findex ()                    /* Function index. */
{
  index_add_arg ("fn");
}

void
cm_tindex ()                    /* Data Type index. */
{
  index_add_arg ("tp");
}

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

/* Force all index entries to be unique. */
void
make_index_entries_unique (array, count)
     INDEX_ELT **array;
     int count;
{
  register int i, j;
  INDEX_ELT **copy;
  int counter = 1;

  copy = (INDEX_ELT **)xmalloc ((1 + count) * sizeof (INDEX_ELT *));

  for (i = 0, j = 0; i < count; i++)
    {
      if ((i == (count - 1)) ||
          (array[i]->node != array[i + 1]->node) ||
          (strcmp (array[i]->entry, array[i + 1]->entry) != 0))
        copy[j++] = array[i];
      else
        {
          free (array[i]->entry);
          free (array[i]);
        }
    }
  copy[j] = (INDEX_ELT *)NULL;

  /* Now COPY contains only unique entries.  Duplicated entries in the
     original array have been freed.  Replace the current array with
     the copy, fixing the NEXT pointers. */
  for (i = 0; copy[i] != (INDEX_ELT *)NULL; i++)
    {

      copy[i]->next = copy[i + 1];

      /* Fix entry names which are the same.  They point to different nodes,
         so we make the entry name unique. */
      if ((copy[i + 1] != (INDEX_ELT *)NULL) &&
          (strcmp (copy[i]->entry, copy[i + 1]->entry) == 0))
        {
          char *new_entry_name;

          new_entry_name = (char *)xmalloc (10 + strlen (copy[i]->entry));
          sprintf (new_entry_name, "%s <%d>", copy[i]->entry, counter);
          free (copy[i]->entry);
          copy[i]->entry = new_entry_name;
          counter++;
        }
      else
        counter = 1;

      array[i] = copy[i];
    }
  array[i] = (INDEX_ELT *)NULL;

  /* Free the storage used only by COPY. */
  free (copy);
}

/* Sort the index passed in INDEX, returning an array of
   pointers to elements.  The array is terminated with a NULL
   pointer.  We call qsort because it's supposed to be fast.
   I think this looks bad. */
INDEX_ELT **
sort_index (index)
     INDEX_ELT *index;
{
  INDEX_ELT **array;
  INDEX_ELT *temp = index;
  int count = 0;
  int save_line_number = line_number;
  char *save_input_filename = input_filename;

  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;

      /* Set line number and input filename to the source line for this
         index entry, as this expansion finds any errors.  */
      line_number = array[count - 1]->defining_line;
      input_filename = array[count - 1]->defining_file;

      /* If this particular entry should be printed as a "code" index,
         then wrap the entry with "@code{...}". */
      array[count - 1]->entry = expansion (temp->entry, index->code);
      
      temp = temp->next;
    }
  array[count] = (INDEX_ELT *) NULL;    /* terminate the array. */
  line_number = save_line_number;
  input_filename = save_input_filename;

  /* Sort the array. */
  qsort (array, count, sizeof (INDEX_ELT *), index_element_compare);
  make_index_entries_unique (array, count);
  return (array);
}

/* Nonzero means that we are in the middle of printing an index. */
int printing_index = 0;

/* Takes one arg, a short name of an index to print.
   Outputs a menu of the sorted elements of the index. */
void
cm_printindex ()
{
  int item;
  INDEX_ELT *index;
  INDEX_ELT **array;
  char *index_name;
  unsigned line_length;
  char *line;
  int saved_inhibit_paragraph_indentation = inhibit_paragraph_indentation;
  int saved_filling_enabled = filling_enabled;

  close_paragraph ();
  get_rest_of_line (&index_name);

  index = index_list (index_name);
  if (index == (INDEX_ELT *)-1)
    {
      line_error (_("Unknown index `%s' in @printindex"), index_name);
      free (index_name);
      return;
    }
  else
    free (index_name);

  /* Do this before sorting, so execute_string in index_element_compare
     will give the same results as when we actually print.  */
  printing_index = 1;
  filling_enabled = 0;
  inhibit_paragraph_indentation = 1;
  array = sort_index (index);

  close_paragraph ();
  add_word (_("* Menu:\n\n"));

#if defined (HAVE_MACROS)
  me_inhibit_expansion++;
#endif /* HAVE_MACROS */

  /* This will probably be enough.  */
  line_length = 100;
  line = xmalloc (line_length);
  
  for (item = 0; (index = array[item]); item++)
    {
      /* A pathological document might have an index entry outside of any
         node.  Don't crash.  Perhaps should warn.  */
      char *index_node = index->node ? index->node : "(none)";
      unsigned new_length = strlen (index->entry);

      if (new_length < 37) /* minimum length used below */
        new_length = 37;
      new_length += strlen (index_node) + 7; /* * : .\n\0 */
      
      if (new_length > line_length)
        {
          line_length = new_length;
          line = xrealloc (line, line_length);
        }

      /* Print the entry, nicely formatted.  We've already expanded any
         commands, including any implicit @code.  Thus, can't call
         execute_string, since @@ has turned into @.  */
      sprintf (line, "* %-37s  %s.\n", index->entry, index_node);
      line[2 + strlen (index->entry)] = ':';
      insert_string (line);

      /* Previous `output_paragraph' from growing to the size of the
         whole index.  */
      flush_output ();
    }

  free (line);
  
#if defined (HAVE_MACROS)
  me_inhibit_expansion--;
#endif /* HAVE_MACROS */

  printing_index = 0;
  free (array);
  close_single_paragraph ();
  filling_enabled = saved_filling_enabled;
  inhibit_paragraph_indentation = saved_inhibit_paragraph_indentation;
}

/* User-defined commands, which happens only from user-defined indexes. */

void
define_user_command (name, proc, needs_braces_p)
     char *name;
     COMMAND_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 = xstrdup (name);
  user_command_array[slot]->proc = proc;
  user_command_array[slot]->argument_in_braces = needs_braces_p;
}

/* 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.

   SeparateNode:
        Make them look like followed references, with the reference
        destinations in a makeinfo manufactured node or,

   EndNode:
        Make them appear at the bottom of the node that they originally
        appeared in. */
#define SeparateNode 0
#define EndNode 1

int footnote_style = EndNode;
int first_footnote_this_node = 1;
int footnote_count = 0;

/* Set the footnote style based on he style identifier in STRING. */
int
set_footnote_style (string)
     char *string;
{
  if ((strcasecmp (string, "separate") == 0) ||
      (strcasecmp (string, "MN") == 0))
    footnote_style = SeparateNode;
  else if ((strcasecmp (string, "end") == 0) ||
           (strcasecmp (string, "EN") == 0))
    footnote_style = EndNode;
  else
    return (-1);

 return (0);
}

void
cm_footnotestyle ()
{
  char *arg;

  get_rest_of_line (&arg);

  /* If set on command line, do not change the footnote style.  */
  if (!footnote_style_preset && set_footnote_style (arg) != 0)
    line_error ("Bad argument to %c%s", COMMAND_PREFIX, command);

  free (arg);
}

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. */
void
remember_note (marker, note)
     char *marker, *note;
{
  FN *temp = (FN *) xmalloc (sizeof (FN));

  temp->marker = xstrdup (marker);
  temp->note = xstrdup (note);
  temp->next = pending_notes;
  pending_notes = temp;
  footnote_count++;
}

/* How to get rid of existing footnotes. */
void
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 = 1;
  footnote_count = 0;
}

/* What to do when you see a @footnote construct. */

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

  get_until ("{", &marker);
  canon_white (marker);

  if (macro_expansion_output_stream && !executing_string)
    append_to_expansion_output (input_text_offset + 1); /* include the { */

  /* Read the argument in braces. */
  if (curchar () != '{')
    {
      line_error (_("`%c%s' needs an argument `{...}', not just `%s'"),
                  COMMAND_PREFIX, command, marker);
      free (marker);
      return;
    }
  else
    {
      int len;
      int braces = 1;
      int loc = ++input_text_offset;

      while (braces)
        {
          if (loc == size_of_input_text)
            {
              line_error (_("No closing brace for footnote `%s'"), marker);
              return;
            }

          if (input_text[loc] == '{')
            braces++;
          else if (input_text[loc] == '}')
            braces--;
          else if (input_text[loc] == '\n')
            line_number++;

          loc++;
        }

      len = (loc - input_text_offset) - 1;
      note = (char *)xmalloc (len + 1);
      strncpy (note, &input_text[input_text_offset], len);
      note[len] = 0;
      input_text_offset = loc;
    }

  /* Must write the macro-expanded argument to the macro expansion
     output stream.  This is like the case in index_add_arg.  */
  if (macro_expansion_output_stream && !executing_string)
    {
      int op_orig;

      remember_itext (input_text, input_text_offset);
      op_orig = output_paragraph_offset;
      me_execute_string (note);
      /* Calling me_execute_string on a lone } provokes an error, since
         as far as the reader knows there is no matching {.  We wrote
         the { above in the call to append_to_expansion_output. */
      write_region_to_macro_output ("}", 0, 1);
      output_paragraph_offset = op_orig;
    }

  if (!current_node || !*current_node)
    {
      line_error (_("Footnote defined without parent node"));
      free (marker);
      free (note);
      return;
    }

  if (!*marker)
    {
      free (marker);

      if (number_footnotes)
        {
          marker = (char *)xmalloc (10);
          sprintf (marker, "%d", current_footnote_number);
          current_footnote_number++;
        }
      else
        marker = xstrdup ("*");
    }

  remember_note (marker, note);

  /* Your method should at least insert MARKER. */
  switch (footnote_style)
    {
    case SeparateNode:
      add_word_args ("(%s)", marker);
      if (first_footnote_this_node)
        {
          char *temp_string;

          temp_string = (char *)
            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 = 0;
        }
      break;

    case EndNode:
      add_word_args ("(%s)", marker);
      break;

    default:
      break;
    }
  free (marker);
  free (note);
}

/* Nonzero means that we are currently in the process of outputting
   footnotes. */
int already_outputting_pending_notes = 0;

/* Output the footnotes.  We are at the end of the current node. */
void
output_pending_notes ()
{
  FN *footnote = pending_notes;

  if (!pending_notes)
    return;

  switch (footnote_style)
    {
    case SeparateNode:
      {
        char *old_current_node = current_node;
        char *old_command = xstrdup (command);

        already_outputting_pending_notes++;
        execute_string ("%cnode %s-Footnotes,,,%s\n",
                        COMMAND_PREFIX, current_node, current_node);
        already_outputting_pending_notes--;
        current_node = old_current_node;
        free (command);
        command = old_command;
      }
      break;

    case EndNode:
      close_paragraph ();
      in_fixed_width_font++;
      execute_string (_("---------- Footnotes ----------\n\n"));
      in_fixed_width_font--;
      break;
    }

  /* Handle the footnotes in reverse order. */
  {
    FN **array = (FN **) xmalloc ((footnote_count + 1) * sizeof (FN *));

    array[footnote_count] = (FN *) NULL;

    while (--footnote_count > -1)
      {
        array[footnote_count] = footnote;
        footnote = footnote->next;
      }

    filling_enabled = 1;
    indented_fill = 1;

    while ((footnote = array[++footnote_count]))
      {
        execute_string ("(%s) %s", footnote->marker, footnote->note);
        close_paragraph ();
      }
    close_paragraph ();
    free (array);
  }
}

/* **************************************************************** */
/*                                                                  */
/*              User definable Macros (text substitution)           */
/*                                                                  */
/* **************************************************************** */

#if defined (HAVE_MACROS)

/* Array of macros and definitions. */
MACRO_DEF **macro_list = (MACRO_DEF **)NULL;

int macro_list_len = 0;         /* Number of elements. */
int macro_list_size = 0;        /* Number of slots in total. */

/* Return the macro definition of NAME or NULL if NAME is not defined. */
MACRO_DEF *
find_macro (name)
     char *name;
{
  register int i;
  register MACRO_DEF *def;

  def = (MACRO_DEF *)NULL;
  for (i = 0; macro_list && (def = macro_list[i]); i++)
    {
      if ((!def->inhibited) && (strcmp (def->name, name) == 0))
        break;
    }
  return (def);
}

/* Add the macro NAME with ARGLIST and BODY to the list of defined macros.
   SOURCE_FILE is the name of the file where this definition can be found,
   and SOURCE_LINENO is the line number within that file.  If a macro already
   exists with NAME, then a warning is produced, and that previous
   definition is overwritten. */
void
add_macro (name, arglist, body, source_file, source_lineno, flags)
     char *name;
     char **arglist;
     char *body;
     char *source_file;
     int source_lineno, flags;
{
  register MACRO_DEF *def;

  def = find_macro (name);

  if (!def)
    {
      if (macro_list_len + 2 >= macro_list_size)
        macro_list = (MACRO_DEF **)xrealloc
          (macro_list, ((macro_list_size += 10) * sizeof (MACRO_DEF *)));

      macro_list[macro_list_len] = (MACRO_DEF *)xmalloc (sizeof (MACRO_DEF));
      macro_list[macro_list_len + 1] = (MACRO_DEF *)NULL;

      def = macro_list[macro_list_len];
      macro_list_len += 1;
      def->name = name;
    }
  else
    {
      char *temp_filename = input_filename;
      int temp_line = line_number;

      warning (_("macro `%s' previously defined"), name);

      input_filename = def->source_file;
      line_number = def->source_lineno;
      warning (_("here is the previous definition of `%s'"), name);

      input_filename = temp_filename;
      line_number = temp_line;

      if (def->arglist)
        {
          register int i;

          for (i = 0; def->arglist[i]; i++)
            free (def->arglist[i]);

          free (def->arglist);
        }
      free (def->source_file);
      free (def->body);
    }

  def->source_file = xstrdup (source_file);
  def->source_lineno = source_lineno;
  def->body = body;
  def->arglist = arglist;
  def->inhibited = 0;
  def->flags = flags;
}

/* Delete the macro with name NAME.  The macro is deleted from the list,
   but it is also returned.  If there was no macro defined, NULL is
   returned. */
MACRO_DEF *
delete_macro (name)
     char *name;
{
  register int i;
  register MACRO_DEF *def;

  def = (MACRO_DEF *)NULL;

  for (i = 0; macro_list && (def = macro_list[i]); i++)
    if (strcmp (def->name, name) == 0)
      {
        memmove (macro_list + i, macro_list + i + 1,
               ((macro_list_len + 1) - i) * sizeof (MACRO_DEF *));
        macro_list_len--;
        break;
      }
  return (def);
}

/* Return the arglist on the current line.  This can behave in two different
   ways, depending on the variable BRACES_REQUIRED_FOR_MACRO_ARGS. */
int braces_required_for_macro_args = 0;

char **
get_macro_args (def)
    MACRO_DEF *def;
{
  register int i;
  char *word;

  /* Quickly check to see if this macro has been invoked with any arguments.
     If not, then don't skip any of the following whitespace. */
  for (i = input_text_offset; i < size_of_input_text; i++)
    if (!cr_or_whitespace (input_text[i]))
      break;

  if (input_text[i] != '{')
    {
      if (braces_required_for_macro_args)
        {
          return ((char **)NULL);
        }
      else
        {
          /* Braces are not required to fill out the macro arguments.  If
             this macro takes one argument, it is considered to be the
             remainder of the line, sans whitespace. */
          if (def->arglist && def->arglist[0] && !def->arglist[1])
            {
              char **arglist;

              get_rest_of_line (&word);
              if (input_text[input_text_offset - 1] == '\n')
                {
                  input_text_offset--;
                  line_number--;
                }
              /* canon_white (word); */
              arglist = (char **)xmalloc (2 * sizeof (char *));
              arglist[0] = word;
              arglist[1] = (char *)NULL;
              return (arglist);
            }
          else
            {
              /* The macro either took no arguments, or took more than
                 one argument.  In that case, it must be invoked with
                 arguments surrounded by braces. */
              return ((char **)NULL);
            }
        }
    }
  return (get_brace_args (def->flags & ME_QUOTE_ARG));
}

/* Substitute actual parameters for named parameters in body.
   The named parameters which appear in BODY must by surrounded
   reverse slashes, as in \foo\. */
char *
apply (named, actuals, body)
     char **named, **actuals, *body;
{
  register int i;
  int new_body_index, new_body_size;
  char *new_body, *text;
  int length_of_actuals;

  length_of_actuals = array_len (actuals);
  new_body_size = strlen (body);
  new_body = (char *)xmalloc (1 + new_body_size);

  /* Copy chars from BODY into NEW_BODY. */
  i = 0; new_body_index = 0;

  while (1)
    {
      if (!body[i])
        break;

      if (body[i] != '\\')
        new_body[new_body_index++] = body[i++];
      else
        {
          /* Snarf parameter name, check against named parameters. */
          char *param;
          int param_start, which, len;

          param_start = ++i;
          while ((body[i]) && (body[i] != '\\'))
            i++;

          len = i - param_start;
          param = (char *)xmalloc (1 + len);
          memcpy (param, body + param_start, len);
          param[len] = 0;

          if (body[i]) /* move past \ */
            i++;

          /* Now check against named parameters. */
          for (which = 0; named && named[which]; which++)
            if (strcmp (named[which], param) == 0)
              break;

          if (named && named[which])
            {
              if (which < length_of_actuals)
                text = actuals[which];
              else
                text = (char *)NULL;

              if (!text)
                text = "";

              len = strlen (text);
            }
          else
            { /* not a parameter, restore \'s */
              i = body[i] ? (i - 1) : i;
              len++;
              text = xmalloc (1 + len);
              sprintf (text, "\\%s", param);
            }

          if ((2 + strlen (param)) < len)
            {
              new_body_size += len + 1;
              new_body = xrealloc (new_body, new_body_size);
            }

          free (param);

          strcpy (new_body + new_body_index, text);
          new_body_index += len;

          if (!named || !named[which])
            free (text);
        }
    }
  new_body[new_body_index] = 0;
  return (new_body);
}

/* Execute the macro passed in DEF, a pointer to a MACRO_DEF.  */
void
execute_macro (def)
     MACRO_DEF *def;
{
  char **arglist;
  int num_args;
  char *execution_string = (char *)NULL;

  if (macro_expansion_output_stream && !executing_string && !me_inhibit_expansion)
    me_append_before_this_command ();

  /* Find out how many arguments this macro definition takes. */
  num_args = array_len (def->arglist);

  /* Gather the arguments present on the line if there are any. */
  arglist = get_macro_args (def);

  if (num_args < array_len (arglist))
    {
      free_array (arglist);
      line_error (_("Macro `%s' called with too many args"), def->name);
      return;
    }

  if (def->body)
    execution_string = apply (def->arglist, arglist, def->body);

  free_array (arglist);

  if (def->body)
    {
      if (macro_expansion_output_stream && !executing_string && !me_inhibit_expansion)
        {
          remember_itext (input_text, input_text_offset);
          me_execute_string (execution_string);
        }
      else
        execute_string ("%s", execution_string);

      free (execution_string);
    }
}

/* Read and remember the definition of a macro. */
void
cm_macro ()
{
  register int i;
  char *name, **arglist, *body, *line;
  int body_size, body_index;
  int depth = 1;
  int defining_line = line_number;
  int flags = 0;

  arglist = (char **)NULL;
  body = (char *)NULL;
  body_size = 0;
  body_index = 0;

  if (macro_expansion_output_stream && !executing_string)
    me_append_before_this_command ();

  skip_whitespace ();

  /* Get the name of the macro.  This is the set of characters which are
     not whitespace and are not `{' immediately following the @macro. */
  {
    int start = input_text_offset;
    int len;

    for (i = start;
         (i < size_of_input_text) &&
         (input_text[i] != '{') &&
         (!cr_or_whitespace (input_text[i]));
         i++);

    len = i - start;
    name = (char *)xmalloc (1 + len);
    strncpy (name, input_text + start, len);
    name[len] = 0;
    input_text_offset = i;
  }

  skip_whitespace ();

  /* It is not required that the definition of a macro includes an arglist.
     If not, don't try to get the named parameters, just use a null list. */
  if (curchar () == '{')
    {
      int arglist_index = 0, arglist_size = 0;
      int gathering_words = 1;
      char *word = (char *)NULL;
      int character;

      /* Read the words inside of the braces which determine the arglist.
         These words will be replaced within the body of the macro at
         execution time. */

      input_text_offset++;
      skip_whitespace_and_newlines ();

      while (gathering_words)
        {
          int len;

          for (i = input_text_offset;
               (character = input_text[i]);
               i++)
            {
              switch (character)
                {
                case '\n':
                  line_number++;
                case ' ':
                case '\t':
                case ',':
                case '}':
                  /* Found the end of the current arglist word.  Save it. */
                  len = i - input_text_offset;
                  word = (char *)xmalloc (1 + len);
                  strncpy (word, input_text + input_text_offset, len);
                  word[len] = 0;
                  input_text_offset = i;

                  /* Advance to the comma or close-brace that signified
                     the end of the argument. */
                  while ((character = curchar ())
                         && character != ','
                         && character != '}')
                    {
                      input_text_offset++;
                      if (character == '\n')
                        line_number++;
                    }

                  /* Add the word to our list of words. */
                  if ((arglist_index + 2) >= arglist_size)
                    arglist = (char **)xrealloc
                      (arglist, (arglist_size += 10) * sizeof (char *));

                  arglist[arglist_index++] = word;
                  arglist[arglist_index] = (char *)NULL;
                  break;
                }

              if (character == '}')
                {
                  input_text_offset++;
                  gathering_words = 0;
                  break;
                }

              if (character == ',')
                {
                  input_text_offset++;
                  skip_whitespace_and_newlines ();
                  i = input_text_offset - 1;
                }
            }
        }
    }

  /* Read the text carefully until we find an "@end macro" which
     matches this one.  The text in between is the body of the macro. */
  skip_whitespace_and_newlines ();

  while (depth)
    {
      if ((input_text_offset + 9) > size_of_input_text)
        {
          int temp_line = line_number;
          line_number = defining_line;
          line_error (_("%cend macro not found"), COMMAND_PREFIX);
          line_number = temp_line;
          return;
        }

      get_rest_of_line (&line);

      /* Handle commands only meaningful within a macro. */
      if ((*line == COMMAND_PREFIX) && (depth == 1) &&
          (strncmp (line + 1, "allow-recursion", 15) == 0) &&
          (line[16] == 0 || whitespace (line[16])))
        {
          for (i = 16; whitespace (line[i]); i++);
          strcpy (line, line + i);
          flags |= ME_RECURSE;
          if (!*line)
            {
              free (line);
              continue;
            }
        }

      if ((*line == COMMAND_PREFIX) && (depth == 1) &&
          (strncmp (line + 1, "quote-arg", 9) == 0) &&
          (line[10] == 0 || whitespace (line[10])))
        {
          for (i = 10; whitespace (line[i]); i++);
          strcpy (line, line + i);

          if (arglist && arglist[0] && !arglist[1])
            {
              flags |= ME_QUOTE_ARG;
              if (!*line)
                {
                  free (line);
                  continue;
                }
            }
          else
            {
              line_error (_("%cquote-arg only useful when the macro takes a single argument"),
                          COMMAND_PREFIX);
            }
        }

      if ((*line == COMMAND_PREFIX) &&
          (strncmp (line + 1, "macro ", 6) == 0))
        depth++;

      if ((*line == COMMAND_PREFIX) &&
          (strncmp (line + 1, "end macro", 9) == 0))
        depth--;

      if (depth)
        {
          if ((body_index + strlen (line) + 3) >= body_size)
            body = (char *)xrealloc
              (body, body_size += 3 + strlen (line));
          strcpy (body + body_index, line);
          body_index += strlen (line);
          body[body_index++] = '\n';
          body[body_index] = 0;
        }
      free (line);
    }

  /* If it was an empty macro like
     @macro foo
     @end macro
     create an empty body.  (Otherwise, the macro is not expanded.)  */
  if (!body)
    {
      body = (char *)malloc(1);
      *body = 0;
    }

  /* We now have the name, the arglist, and the body.  However, BODY
     includes the final newline which preceded the `@end macro' text.
     Delete it. */
  if (body && strlen (body))
    body[strlen (body) - 1] = 0;

  add_macro (name, arglist, body, input_filename, defining_line, flags);

  if (macro_expansion_output_stream && !executing_string)
    remember_itext (input_text, input_text_offset);
}

void
cm_unmacro ()
{
  register int i;
  char *line, *name;
  MACRO_DEF *def;

  if (macro_expansion_output_stream && !executing_string)
    me_append_before_this_command ();

  get_rest_of_line (&line);

  for (i = 0; line[i] && !whitespace (line[i]); i++);
  name = (char *)xmalloc (i + 1);
  strncpy (name, line, i);
  name[i] = 0;

  def = delete_macro (name);

  if (def)
    {
      free (def->source_file);
      free (def->name);
      free (def->body);

      if (def->arglist)
        {
          register int i;

          for (i = 0; def->arglist[i]; i++)
            free (def->arglist[i]);

          free (def->arglist);
        }

      free (def);
    }

  free (line);
  free (name);

  if (macro_expansion_output_stream && !executing_string)
    remember_itext (input_text, input_text_offset);
}

/* How to output sections of the input file verbatim. */

/* Set the value of POINTER's offset to OFFSET. */
ITEXT *
remember_itext (pointer, offset)
     char *pointer;
     int offset;
{
  register int i;
  ITEXT *itext = (ITEXT *)NULL;

  /* If we have no info, initialize a blank list. */
  if (!itext_info)
    {
      itext_info = (ITEXT **)xmalloc ((itext_size = 10) * sizeof (ITEXT *));
      for (i = 0; i < itext_size; i++)
        itext_info[i] = (ITEXT *)NULL;
    }

  /* If the pointer is already present in the list, then set the offset. */
  for (i = 0; i < itext_size; i++)
    if ((itext_info[i] != (ITEXT *)NULL) &&
        (itext_info[i]->pointer == pointer))
      {
        itext = itext_info[i];
        itext_info[i]->offset = offset;
        break;
      }

  if (i == itext_size)
    {
      /* Find a blank slot (or create a new one), and remember the
         pointer and offset. */
      for (i = 0; i < itext_size; i++)
        if (itext_info[i] == (ITEXT *)NULL)
          break;

      /* If not found, then add some slots. */
      if (i == itext_size)
        {
          register int j;

          itext_info = (ITEXT **)xrealloc
            (itext_info, (itext_size += 10) * sizeof (ITEXT *));

          for (j = i; j < itext_size; j++)
            itext_info[j] = (ITEXT *)NULL;
        }

      /* Now add the pointer and the offset. */
      itext_info[i] = (ITEXT *)xmalloc (sizeof (ITEXT));
      itext_info[i]->pointer = pointer;
      itext_info[i]->offset = offset;
      itext = itext_info[i];
    }
  return (itext);
}

/* Forget the input text associated with POINTER. */
void
forget_itext (pointer)
     char *pointer;
{
  register int i;

  for (i = 0; i < itext_size; i++)
    if (itext_info[i] && (itext_info[i]->pointer == pointer))
      {
        free (itext_info[i]);
        itext_info[i] = (ITEXT *)NULL;
        break;
      }
}

/* Append the text which appeared in input_text from the last offset to
   the character just before the command that we are currently executing. */
void
me_append_before_this_command ()
{
  register int i;

  for (i = input_text_offset; i && (input_text[i] != COMMAND_PREFIX); i--);
  maybe_write_itext (input_text, i);
}

/* Similar to execute_string (), but only takes a single string argument,
   and remembers the input text location, etc. */
void
me_execute_string (execution_string)
     char *execution_string;
{
  pushfile ();
  input_text_offset = 0;
  input_text = execution_string;
  input_filename = xstrdup (input_filename);
  size_of_input_text = strlen (execution_string);

  remember_itext (execution_string, 0);

  me_executing_string++;
  reader_loop ();
  popfile ();
  me_executing_string--;
}

/* Append the text which appears in input_text from the last offset to
   the current OFFSET. */
void
append_to_expansion_output (offset)
     int offset;
{
  register int i;
  ITEXT *itext = (ITEXT *)NULL;

  for (i = 0; i < itext_size; i++)
    if (itext_info[i] && itext_info[i]->pointer == input_text)
      {
        itext = itext_info[i];
        break;
      }

  if (!itext)
    return;

  if (offset > itext->offset)
    {
      write_region_to_macro_output
        (input_text, itext->offset, offset);
      remember_itext (input_text, offset);
    }
}

/* Only write this input text iff it appears in our itext list. */
void
maybe_write_itext (pointer, offset)
     char *pointer;
     int offset;
{
  register int i;
  ITEXT *itext = (ITEXT *)NULL;

  for (i = 0; i < itext_size; i++)
    if (itext_info[i] && (itext_info[i]->pointer == pointer))
      {
        itext = itext_info[i];
        break;
      }

  if (itext && (itext->offset < offset))
    {
      write_region_to_macro_output (itext->pointer, itext->offset, offset);
      remember_itext (pointer, offset);
    }
}

void
write_region_to_macro_output (string, start, end)
     char *string;
     int start, end;
{
  if (macro_expansion_output_stream)
    fwrite (string + start, 1, end - start, macro_expansion_output_stream);
}

#endif /* HAVE_MACROS */

/* Return the length of the array in ARRAY. */
int
array_len (array)
     char **array;
{
  register int i = 0;

  if (array)
    for (i = 0; array[i] != (char *)NULL; i++);

  return (i);
}

void
free_array (array)
     char **array;
{
  if (array)
    {
      register int i;

      for (i = 0; array[i] != (char *)NULL; i++)
        free (array[i]);

      free (array);
    }
}

/* Function is used even when we don't have macros.  Although, I have
   to admit, it is unlikely that you would have a use for it if you
   aren't using macros. */
char **
get_brace_args (quote_single)
     int quote_single;
{
  char **arglist, *word;
  int arglist_index, arglist_size;
  int character, escape_seen, start;
  int depth = 1;

  /* There is an arglist in braces here, so gather the args inside of it. */
  skip_whitespace_and_newlines ();
  input_text_offset++;
  arglist = (char **)NULL;
  arglist_index = arglist_size = 0;

 get_arg:
  skip_whitespace_and_newlines ();
  start = input_text_offset;
  escape_seen = 0;

  while ((character = curchar ()))
    {
      if (character == '\\')
        {
          input_text_offset += 2;
          escape_seen = 1;
        }
      else if (character == '{')
        {
          depth++;
          input_text_offset++;
        }
      else if ((character == ',' && !quote_single) ||
               ((character == '}') && depth == 1))
        {
          int len = input_text_offset - start;

          if (len || (character != '}'))
            {
              word = (char *)xmalloc (1 + len);
              strncpy (word, input_text + start, len);
              word[len] = 0;

              /* Clean up escaped characters. */
              if (escape_seen)
                {
                  register int i;

                  for (i = 0; word[i]; i++)
                    if (word[i] == '\\')
                      memmove (word + i, word + i + 1,
                               1 + strlen (word + i + 1));
                }

              if (arglist_index + 2 >= arglist_size)
                arglist = (char **)xrealloc
                  (arglist, (arglist_size += 10) * sizeof (char *));

              arglist[arglist_index++] = word;
              arglist[arglist_index] = (char *)NULL;
            }

          input_text_offset++;
          if (character == '}')
            break;
          else
            goto get_arg;
        }
      else if (character == '}')
        {
          depth--;
          input_text_offset++;
        }
      else
        {
          input_text_offset++;
          if (character == '\n') line_number++;
        }
    }
  return (arglist);
}

/* **************************************************************** */
/*                                                                  */
/*                  Looking For Include Files                       */
/*                                                                  */
/* **************************************************************** */

/* Given a string containing units of information separated by colons,
   return the next one pointed to by INDEX, or NULL if there are no more.
   Advance INDEX to the character after the colon. */
char *
extract_colon_unit (string, index)
     char *string;
     int *index;
{
  int i, start;

  i = *index;

  if (!string || (i >= strlen (string)))
    return ((char *)NULL);

  /* Each call to this routine leaves the index pointing at a colon if
     there is more to the path.  If I is > 0, then increment past the
     `:'.  If I is 0, then the path has a leading colon.  Trailing colons
     are handled OK by the `else' part of the if statement; an empty
     string is returned in that case. */
  if (i && string[i] == ':')
    i++;

  start = i;

  while (string[i] && string[i] != ':') i++;

  *index = i;

  if (i == start)
    {
      if (string[i])
        (*index)++;

      /* Return "" in the case of a trailing `:'. */
      return (xstrdup (""));
    }
  else
    {
      char *value;

      value = (char *)xmalloc (1 + (i - start));
      strncpy (value, &string[start], (i - start));
      value [i - start] = 0;

      return (value);
    }
}

/* Return the full pathname for FILENAME by searching along PATH.
   When found, return the stat () info for FILENAME in FINFO.
   If PATH is NULL, only the current directory is searched.
   If the file could not be found, return a NULL pointer. */
char *
get_file_info_in_path (filename, path, finfo)
     char *filename, *path;
     struct stat *finfo;
{
  char *dir;
  int result, index = 0;

  if (path == (char *)NULL)
    path = ".";

  /* Handle absolute pathnames. "./foo", "/foo", "../foo". */
  if (*filename == '/' ||
      (*filename == '.' &&
       (filename[1] == '/' ||
        (filename[1] == '.' && filename[2] == '/')))
#ifdef WIN32
      /* Handle names that look like "d:/foo/bar" */
      || (isalpha (*filename) && filename [1] == ':' 
          && (filename [2] == '/' || filename [2] == '\\'))
#endif
     )
    {
      if (stat (filename, finfo) == 0)
        return (xstrdup (filename));
      else
        return ((char *)NULL);
    }

  while ((dir = extract_colon_unit (path, &index)))
    {
      char *fullpath;

      if (!*dir)
        {
          free (dir);
          dir = xstrdup (".");
        }

      fullpath = (char *)xmalloc (2 + strlen (dir) + strlen (filename));
      sprintf (fullpath, "%s/%s", dir, filename);
      free (dir);

      result = stat (fullpath, finfo);

      if (result == 0)
        return (fullpath);
      else
        free (fullpath);
    }
  return NULL;
}
