/*
 *  main.c
 *  
 *  This program error checks the OAR PDL and converts it into 
 *  It works by reading the input file into a linked list of lines
 *  and then performing sweeps on that list until all formatting is
 *  complete.
 *
 *  FEATURES:
 *    + rudimentary statistics
 *    + forgiveness features 
 *    + output in Texinfo format
 *    + output in a form that a sibling MS-Word VBA program can format
 * 
 *  CHECKS PERFORMED:
 *    + unable to open file
 *    + unexpected end of file
 *    + line should have a colon
 *    + basic text (to right or below) associated with keyword
 *      improperly placed
 *    + an "incomplete marker" is still in place
 *    + missing keywords within a subsection
 *    + duplicated keywords withing a subsection
 *    + subsections in correct order
 *    + section header indicates no subsections and there are subsections
 *    + section header indicates subsections and there are no subsections
 *    + inconsistent spacing in RAW text.  This tends to be 1st line with
 *      text is indented further than a subsequent line.
 *    + missing components on line (incomplete)
 *    + invalid repitition of a subsection
 *    + type keyword validated for class, type, and spacing between
 *    + both members and range are present
 *    + neither members and range are present
 *    + enumerated types and attributes have members
 *    + non-enumerated types and attributes have ranges.
 *    + Object name and end object have the same name
 *    + booleans in attribute section list both true and false
 *    + task synchronization keyword is checked.  There must be a valid 
 *      type of synchronization primitive and a description when expected.
 *    + sections in correct order
 *
 *  INTERNAL ERRORS:
 *    + attempting to reformat an already formatted line
 *    + end of processing reached and no formatting assigned to line
 *
 *  CHECKS NOT PERFORMED:
 *
 *  TODO:
 *
 *  IDEAS NOT IMPLEMENTED:
 *    + smarter reporting of sections not in the right order
 *    + task which uses multiple synchronization types
 *    + improved error messages
 *    + no introductions inserted to sections
 *    + recognize special "symbols" like infinity, +-, etc.
 *
 *  QUESTIONS:
 *    + "what do I know" is actually a table and should not be reformatted.
 *
 *  COPYRIGHT (c) 1997.
 *  On-Line Applications Research Corporation (OAR).
 *  All rights reserved.
 *
 *  $Id$
 */

#include <assert.h>
#include <ctype.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* XXX -- just for testing -- these should be set by options */
char DocsNextNode[256]     = "";
char DocsPreviousNode[256] = "";
char DocsUpNode[256]       = "";

extern int   optind;     /* Why is this not in <stdlib.h>? */
extern char *optarg;     /* Why is this not in <stdlib.h>? */

#ifndef NAME_MAX
#define NAME_MAX      14 /* Why is the one in limits.h not showing up? */
#endif
#define INIT_DATA
#define EXTERN

#include "base.h"

FILE           *OutFile = stdout;

/*************************************************************************
 *************************************************************************
 *****                 DATA TYPES AND CONSTANT TABLES                *****
 *************************************************************************
 *************************************************************************/
/*
 *  Usage Information
 */

char *Usage_Strings[] = {
  "\n",
  "usage: cmd [-vti?] [-p previous_node] [-u up_node] files ...\n", 
  "\n",
  "EOF"
};

/*
 *  The page separator is not really a keyword and will be purged before
 *  it is seen elsewhere.
 */

#define PAGE_SEPARATOR                            "#PAGE"

/* 
 *  Section Delimiter Keywords
 */

#define MAXIMUM_KEYWORD_LENGTH   32

/*
 *  Level indicates where in the format the delimiter is allowed to occur.
 *    1 indicates a major section divider (e.g. "ATTRIBUTE DESCRIPTIONS:").
 *    2 indicates a subsection (e.g. "ATTRIBUTE:"). 
 *    3 indicates a heading (e.g. "DESCRIPTION:").
 */

#define TEXT         0
#define SECTION      1
#define SUBSECTION   2
#define HEADING      3
  
typedef enum {
  UNUSED,                            /* dummy 0 slot */
  OBJECT,                            /* sections */
  ATTRIBUTE_DESCRIPTIONS,
  ASSOCIATION_DESCRIPTIONS,
  ABSTRACT_TYPE_DESCRIPTIONS,
  DATA_ITEM_DESCRIPTIONS,
  METHOD_DESCRIPTIONS,
  TASK_DESCRIPTIONS,
  END_OBJECT,
 
  ATTRIBUTE,                         /* subsections */
  ASSOCIATION,
  ABSTRACT_TYPE,
  DATA_ITEM,
  METHOD,
  TASK,
 
  DESCRIPTION,                       /* headings */
  COPYRIGHT,
  PORTING,
  THEORY_OF_OPERATION,
  DERIVATION,
  DEPENDENCIES,
  NOTES,
  TYPE,
  RANGE,
  MEMBERS,
  UNITS,
  SCALE_FACTOR,
  DEFAULT,
  TOLERANCE,
  REQUIREMENTS,
  REFERENCES,
  VISIBILITY,
  ASSOCIATED_WITH,
  MULTIPLICITY,
  INPUTS,
  OUTPUTS,
  PDL,
  SYNCHRONIZATION,
  TIMING,

  RAW_EXAMPLE,                       /* our own */
  
}  Keyword_indices_t;

#define KEYWORD_FIRST OBJECT
#define KEYWORD_LAST  TIMING

/*
 *  Line Management Structure
 */

typedef enum {
  NO_EXTRA_FORMATTING_INFO,
  RAW_OUTPUT,
  PARAGRAPH_OUTPUT,
  BULLET_OUTPUT,
}  ExtraFormat_info_t;

typedef struct {
  Chain_Node         Node;
  Keyword_indices_t  keyword;   /* unused is unknown/undecided */
  ExtraFormat_info_t format;
  int                number;
  char               Contents[ PARAGRAPH_SIZE ];
} Line_Control;

typedef enum {
  RT_NO_CHECK,      /* don't do this at all */
  RT_FORBIDDEN,     /* no text to right allowed */
  RT_OPTIONAL,      /* text to right optional -- none below */
  RT_NONE,          /* text to right is "none" or nothing -- none below */
  RT_REQUIRED,      /* text to right required -- none below */
  RT_BELOW,         /* text to right forbidden -- text below required */
  RT_NONE_OR_BELOW, /* text to right is "none" OR there is text below  */
  RT_MAYBE_BELOW,   /* text to right required -- text below optional */
  RT_EITHER,        /* text to right OR below */
  RT_BOTH           /* text to right AND below */
}  Keywords_text_mode_t;

typedef enum {
  BL_FORBIDDEN,     /* text below forbidden */
  BL_FORMATTED,     /* text below is to be formatted as paragraphs */
  BL_RAW,           /* text below is to be unprocessed by this program */
}  Keywords_text_below_t;

typedef int (*Keyword_validater_t)( Line_Control * );

typedef struct {
  char                      Name[ MAXIMUM_KEYWORD_LENGTH ];
  int                       level;
  Keywords_text_mode_t      text_mode;
  Keywords_text_below_t     text_below_mode;
  Keyword_validater_t       keyword_validation_routine;
}  Keyword_info_t;

/*
 *  Keyword Validation Routines
 */

int Validate_visibility(
  Line_Control   *line
);

int Validate_synchronization(
  Line_Control   *line
);

Keyword_info_t Keywords[] = {
  { "unused",
        0,          0,                0, NULL }, /* so 0 can be invalid */
  { "OBJECT:",
        SECTION,    RT_REQUIRED,      BL_FORBIDDEN, NULL },
  { "ATTRIBUTE DESCRIPTIONS:",
        SECTION,    RT_NONE,          BL_FORBIDDEN, NULL },
  { "ASSOCIATION DESCRIPTIONS:",
        SECTION,    RT_NONE,          BL_FORBIDDEN, NULL },
  { "ABSTRACT TYPE DESCRIPTIONS:",
        SECTION,    RT_NONE,          BL_FORBIDDEN, NULL },
  { "DATA ITEM DESCRIPTIONS:",
        SECTION,    RT_NONE,          BL_FORBIDDEN, NULL },
  { "METHOD DESCRIPTIONS:",
        SECTION,    RT_NONE,          BL_FORBIDDEN, NULL },
  { "TASK DESCRIPTIONS:",
        SECTION,    RT_NONE,          BL_FORBIDDEN, NULL },
  { "ENDOBJECT:",
        SECTION,    RT_REQUIRED,      BL_FORBIDDEN, NULL },

  { "ATTRIBUTE:",
        SUBSECTION, RT_REQUIRED,      BL_FORBIDDEN, NULL },
  { "ASSOCIATION:",
        SUBSECTION, RT_REQUIRED,      BL_FORBIDDEN, NULL },
  { "ABSTRACT TYPE:",
        SUBSECTION, RT_REQUIRED,      BL_FORBIDDEN, NULL },
  { "DATA ITEM:",
        SUBSECTION, RT_REQUIRED,      BL_FORBIDDEN, NULL },
  { "METHOD:",
        SUBSECTION, RT_REQUIRED,      BL_FORBIDDEN, NULL },
  { "TASK:",
        SUBSECTION, RT_REQUIRED,      BL_FORBIDDEN, NULL },

  { "DESCRIPTION:",
        HEADING,    RT_BELOW,         BL_FORMATTED, NULL },
  { "COPYRIGHT:",
        HEADING,    RT_BELOW,         BL_FORMATTED, NULL },
  { "PORTING:",
        HEADING,    RT_BELOW,         BL_FORMATTED, NULL },
  { "THEORY OF OPERATION:",
        HEADING,    RT_BELOW,         BL_FORMATTED, NULL },
  { "DERIVATION:",
        HEADING,    RT_REQUIRED,      BL_FORBIDDEN, NULL },
  { "DEPENDENCIES:",
        HEADING,    RT_BELOW,         BL_FORMATTED, NULL },
  { "NOTES:",
        HEADING,    RT_BELOW,         BL_FORMATTED, NULL },
  { "TYPE:",
        HEADING,    RT_REQUIRED,      BL_FORBIDDEN, NULL },
  { "RANGE:",
        HEADING,    RT_REQUIRED,      BL_FORBIDDEN, NULL },
  { "MEMBERS:",
        HEADING,    RT_BELOW,         BL_RAW,       NULL },
  { "UNITS:",
        HEADING,    RT_REQUIRED,      BL_FORBIDDEN, NULL },
  { "SCALE FACTOR:",
        HEADING,    RT_REQUIRED,      BL_FORBIDDEN, NULL },
  { "DEFAULT:",
        HEADING,    RT_EITHER,        BL_RAW, NULL },
  { "TOLERANCE:",
        HEADING,    RT_REQUIRED,      BL_FORBIDDEN, NULL },
  { "REQUIREMENTS:",
        HEADING,    RT_BELOW,         BL_RAW,       NULL },
  { "REFERENCES:",
        HEADING,    RT_BELOW,         BL_RAW,       NULL },
  { "VISIBILITY:",
        HEADING,    RT_REQUIRED,      BL_FORBIDDEN, Validate_visibility },
  { "ASSOCIATED WITH:",
        HEADING,    RT_REQUIRED,      BL_FORBIDDEN, NULL },
  { "MULTIPLICITY:",
        HEADING,    RT_REQUIRED,      BL_FORBIDDEN, NULL },
  { "INPUTS:",
        HEADING,    RT_NONE_OR_BELOW, BL_RAW,       NULL },
  { "OUTPUTS:",
        HEADING,    RT_NONE_OR_BELOW, BL_RAW,       NULL },
  { "PDL:",
        HEADING,    RT_BELOW,         BL_RAW,       NULL },
  { "SYNCHRONIZATION:",
        HEADING,    RT_MAYBE_BELOW,   BL_RAW,    Validate_synchronization },
  { "TIMING:",
        HEADING,    RT_REQUIRED,      BL_FORBIDDEN, NULL },
  { "",
        TEXT,       RT_NO_CHECK,   BL_RAW,       NULL }
};

#define NUMBER_OF_KEYWORDS \
  ( sizeof( Keywords ) / sizeof( Keyword_info_t ) ) - 2

/*
 *  Section Descriptions
 */

#define MAXIMUM_ELEMENTS 16

typedef struct {
  Keyword_indices_t  keyword;
  boolean            is_required;
}  Element_info_t;

typedef struct Section_info_struct_t Section_info_t;

typedef (*Section_validater_t)(
  Section_info_t *,
  Line_Control *,  /* start */
  Line_Control *   /* next_section */
);

struct Section_info_struct_t {
  boolean              repeats;
  Keyword_indices_t    This_section;
  Keyword_indices_t    Next_section;
  Section_validater_t  section_validation_routine;
  Element_info_t       Description[MAXIMUM_ELEMENTS];
};

int Validate_object(
  Section_info_t *section,
  Line_Control   *start,
  Line_Control   *next_section
);
  
int Validate_attribute(
  Section_info_t *section,
  Line_Control   *start,
  Line_Control   *next_section
);
  
int Validate_abstract_type(
  Section_info_t *section,
  Line_Control   *start,
  Line_Control   *next_section
);
  

Section_info_t ObjectSection = {
  FALSE,                        /* subsections repeat */
  OBJECT,                       /* this section */
  ATTRIBUTE_DESCRIPTIONS,       /* next section */
  Validate_object,              /* validation routine */
  {
    { OBJECT,              TRUE },     /*  0 */
    { DESCRIPTION,         TRUE },     /*  1 */
    { COPYRIGHT,           FALSE },    /*  2 */
    { PORTING,             FALSE },    /*  3 */
    { THEORY_OF_OPERATION, TRUE },     /*  4 */
    { DERIVATION,          FALSE },    /*  5 */
    { DEPENDENCIES,        FALSE },    /*  6 */
    { REQUIREMENTS,        FALSE },    /*  7 */
    { REFERENCES,          FALSE },    /*  8 */
    { NOTES,               FALSE },    /*  9 */
    { UNUSED,              FALSE },    /* 10 */
    { UNUSED,              FALSE },    /* 11 */
    { UNUSED,              FALSE },    /* 12 */
    { UNUSED,              FALSE },    /* 13 */
    { UNUSED,              FALSE },    /* 14 */
    { UNUSED,              FALSE },    /* 15 */
  }
};

Section_info_t AttributeSection = {
  TRUE,                         /* subsections repeat */
  ATTRIBUTE_DESCRIPTIONS,       /* this section */
  ASSOCIATION_DESCRIPTIONS,     /* next section */
  Validate_attribute,           /* validation routine */
  {
    { ATTRIBUTE,           TRUE },     /*  0 */
    { DESCRIPTION,         TRUE },     /*  1 */
    { TYPE,                TRUE },     /*  2 */
    { MEMBERS,             FALSE },    /*  3 */
    { RANGE,               FALSE },    /*  4 */
    { UNITS,               FALSE },    /*  5 */
    { SCALE_FACTOR,        FALSE },    /*  6 */
    { DEFAULT,             FALSE },    /*  7 */
    { TOLERANCE,           FALSE },    /*  8 */
    { REQUIREMENTS,        FALSE },    /*  9 */
    { REFERENCES,          FALSE },    /* 10 */
    { NOTES,               FALSE },    /* 11 */
    { UNUSED,              FALSE },    /* 12 */
    { UNUSED,              FALSE },    /* 13 */
    { UNUSED,              FALSE },    /* 14 */
    { UNUSED,              FALSE },    /* 15 */
  }
};

Section_info_t AssociationSection = {
  TRUE,                         /* subsections repeat */
  ASSOCIATION_DESCRIPTIONS,     /* this section */
  ABSTRACT_TYPE_DESCRIPTIONS,   /* next section */
  NULL,                         /* validation routine */
  {
    { ASSOCIATION,         TRUE },     /*  0 */
    { DESCRIPTION,         TRUE },     /*  1 */
    { VISIBILITY,          TRUE },     /*  2 */
    { ASSOCIATED_WITH,     TRUE },     /*  3 */
    { MULTIPLICITY,        TRUE },     /*  4 */
    { REQUIREMENTS,        FALSE },    /*  5 */
    { REFERENCES,          FALSE },    /*  6 */
    { NOTES,               FALSE },    /*  7 */
    { UNUSED,              FALSE },    /*  8 */
    { UNUSED,              FALSE },    /*  9 */
    { UNUSED,              FALSE },    /* 10 */
    { UNUSED,              FALSE },    /* 11 */
    { UNUSED,              FALSE },    /* 12 */
    { UNUSED,              FALSE },    /* 13 */
    { UNUSED,              FALSE },    /* 14 */
    { UNUSED,              FALSE },    /* 15 */
  }
};

Section_info_t AbstractTypeSection = {
  TRUE,                         /* subsections repeat */
  ABSTRACT_TYPE_DESCRIPTIONS,   /* this section */
  DATA_ITEM_DESCRIPTIONS,       /* next section */
  Validate_abstract_type,       /* validation routine */
  {
    { ABSTRACT_TYPE,       TRUE },     /*  0 */
    { DESCRIPTION,         TRUE },     /*  1 */
    { VISIBILITY,          TRUE },     /*  2 */
    { DERIVATION,          TRUE },     /*  3 */
    { MEMBERS,             FALSE },    /*  4 */
    { RANGE,               FALSE },    /*  5 */
    { UNITS,               FALSE },    /*  6 */
    { SCALE_FACTOR,        FALSE },    /*  7 */
    { DEFAULT,             FALSE },    /*  8 */
    { TOLERANCE,           FALSE },    /*  9 */
    { REQUIREMENTS,        FALSE },    /* 10 */
    { REFERENCES,          FALSE },    /* 11 */
    { NOTES,               FALSE },    /* 12 */
    { UNUSED,              FALSE },    /* 13 */
    { UNUSED,              FALSE },    /* 14 */
    { UNUSED,              FALSE },    /* 15 */
  }
};

Section_info_t DataItemSection = {
  TRUE,                         /* subsections repeat */
  DATA_ITEM_DESCRIPTIONS,       /* this section */
  METHOD_DESCRIPTIONS,          /* next section */
  NULL,                         /* validation routine */
  {
    { DATA_ITEM,           TRUE },     /*  0 */
    { DESCRIPTION,         TRUE },     /*  1 */
    { TYPE,                TRUE },     /*  2 */
    { UNITS,               FALSE },    /*  3 */
    { SCALE_FACTOR,        FALSE },    /*  4 */
    { DEFAULT,             FALSE },    /*  5 */
    { TOLERANCE,           FALSE },    /*  6 */
    { NOTES,               FALSE },    /*  7 */
    { UNUSED,              FALSE },    /*  8 */
    { UNUSED,              FALSE },    /*  9 */
    { UNUSED,              FALSE },    /* 10 */
    { UNUSED,              FALSE },    /* 11 */
    { UNUSED,              FALSE },    /* 12 */
    { UNUSED,              FALSE },    /* 13 */
    { UNUSED,              FALSE },    /* 14 */
    { UNUSED,              FALSE },    /* 15 */
  }
};

Section_info_t MethodSection = {
  TRUE,                         /* subsections repeat */
  METHOD_DESCRIPTIONS,          /* this section */
  TASK_DESCRIPTIONS,            /* next section */
  NULL,                         /* validation routine */
  {
    { METHOD,              TRUE },     /*  0 */
    { DESCRIPTION,         TRUE },     /*  1 */
    { VISIBILITY,          TRUE },     /*  2 */
    { INPUTS,              TRUE },     /*  3 */
    { OUTPUTS,             TRUE },     /*  4 */
    { REQUIREMENTS,        FALSE },    /*  5 */
    { REFERENCES,          FALSE },    /*  6 */
    { NOTES,               FALSE },    /*  7 */
    { PDL,                 TRUE },     /*  8 */
    { UNUSED,              FALSE },    /*  9 */
    { UNUSED,              FALSE },    /* 10 */
    { UNUSED,              FALSE },    /* 11 */
    { UNUSED,              FALSE },    /* 12 */
    { UNUSED,              FALSE },    /* 13 */
    { UNUSED,              FALSE },    /* 14 */
    { UNUSED,              FALSE },    /* 15 */
  }
};

Section_info_t TaskSection = {
  TRUE,                         /* subsections repeat */
  TASK_DESCRIPTIONS,            /* this section */
  END_OBJECT,                   /* next section */
  NULL,                         /* validation routine */
  {
    { METHOD,              TRUE },     /*  0 */
    { DESCRIPTION,         TRUE },     /*  1 */
    { VISIBILITY,          TRUE },     /*  2 */
    { INPUTS,              TRUE },     /*  3 */
    { SYNCHRONIZATION,     TRUE },     /*  4 */
    { TIMING,              TRUE },     /*  5 */
    { REQUIREMENTS,        FALSE },    /*  6 */
    { REFERENCES,          FALSE },    /*  7 */
    { NOTES,               FALSE },    /*  8 */
    { PDL,                 TRUE },     /*  9 */
    { UNUSED,              FALSE },    /* 10 */
    { UNUSED,              FALSE },    /* 11 */
    { UNUSED,              FALSE },    /* 12 */
    { UNUSED,              FALSE },    /* 13 */
    { UNUSED,              FALSE },    /* 14 */
    { UNUSED,              FALSE },    /* 15 */
  }
};


Section_info_t *Sections[] = {
  &ObjectSection,
  &AttributeSection,
  &AssociationSection,
  &AbstractTypeSection,
  &DataItemSection,
  &MethodSection,
  &TaskSection
};

/*
 *  exit_application
 */

void exit_application( 
  int status
)
{
  fprintf( stderr, "*** Error encountered ***\n" );
/*
  fprintf( stderr, "*** Error encountered on line %d ***\n", CurrentLine );
*/
  fclose( OutFile ); 
  exit( 1 );
}

/*************************************************************************
 *************************************************************************
 *****                LINE MANIPULATION ROUTINES                     *****
 *************************************************************************
 *************************************************************************/

/*
 * PrintLine
 */

void PrintLine(
  Line_Control *line
)
{
  assert( line );

  if ( line->number == -1 )
    fprintf( stderr, "     " );
  else
    fprintf( stderr, "%5d", line->number );

#if 0
  fprintf( stderr, "%s\n", line->Contents );
#else
  /* 
   *  Include some debugging information
   */
  fprintf(
    stderr,
    "<%d,%d>:%s\n", 
    line->keyword,
    line->format,
    line->Contents
  );
#endif
}

Chain_Control Line_Pool;

/*
 *  FillLinePool
 */

void FillLinePool( void )
{
  void *pool;

#define LINE_POOL_FILL_COUNT 100

  pool = malloc( sizeof( Line_Control ) * LINE_POOL_FILL_COUNT );
  assert( pool );

  _Chain_Initialize(
    &Line_Pool,
    pool,
    LINE_POOL_FILL_COUNT,
    sizeof( Line_Control )
  );
}

/*
 * AllocateLine
 */

Line_Control *AllocateLine( void )
{
  Line_Control  *new_line;

  new_line = (Line_Control *) _Chain_Get( &Line_Pool );
  if ( !new_line ) {
    FillLinePool();
    new_line = (Line_Control *) _Chain_Get( &Line_Pool );
    assert( new_line );
  }
 
/*
 *  This is commented out because although it is helpful during debug,
 *  it consumes a significant percentage of the program's execution time.
 */

  memset( new_line->Contents, '\0', sizeof( new_line->Contents ) );
  new_line->number = -1;

  new_line->keyword = UNUSED;
  new_line->format = NO_EXTRA_FORMATTING_INFO;

  new_line->Node.next     = NULL;
  new_line->Node.previous = NULL;

  return new_line;
}

/*
 * FreeLine
 */

void FreeLine(
  Line_Control *line
)
{
  fflush( stdout );
  _Chain_Append( &Line_Pool, &line->Node );
}

/*
 * DeleteLine
 */

Line_Control *DeleteLine(
  Line_Control *line
)
{
  Line_Control *next;
  
  next = (Line_Control *)line->Node.next; 
  _Chain_Extract( &line->Node );
  FreeLine( line );
  return next;
}

/* 
 *  PrintSurroundingLines
 */

void PrintSurroundingLines(
  Line_Control *line,
  int           backward,
  int           forward
)
{
  int           i;
  int           real_backward;
  Line_Control *local;

  for ( local=line, real_backward=0, i=1 ; 
        i<=backward ; 
        i++, real_backward++ ) {
    if ( &local->Node == Lines.first )
      break;
    local = (Line_Control *) local->Node.previous;
  }

  for ( i=1 ; i<=real_backward ; i++ ) {
    PrintLine( local );
    local = (Line_Control *) local->Node.next;
  }
 
  PrintLine( local );

  for ( i=1 ; i<=forward ; i++ ) {
    local = (Line_Control *) local->Node.next;
    if ( _Chain_Is_last( &local->Node ) )
      break;
    PrintLine( local );
  }
 
}

/*
 *  SetLineFormat
 */

void SetLineFormat(
  Line_Control       *line,
  ExtraFormat_info_t  format
)
{
  if ( line->format != NO_EXTRA_FORMATTING_INFO ) {
    fprintf( stderr, "Line %d is already formatted\n", line->number );
    PrintLine( line );
    assert( FALSE );
  }

  line->format = format;
}

/*
 *  LineCopyFromRight
 */

void LineCopyFromRight(
  Line_Control *line,
  char         *dest
)
{
  char *p;

  for ( p=line->Contents ; *p != ':' ; p++ ) 
    ;
  p++;  /* skip the ':' */
  for ( ; isspace( *p ) ; p++ )
    ;

  strcpy( dest, p );

}

/*
 *  LineCopySectionName
 */

void LineCopySectionName(
  Line_Control *line,
  char         *dest
)
{
  char *p;
  char *d;

  p = line->Contents;
  d = dest;

  if ( *p == '@' ) {                /* skip texinfo command */
    while ( !isspace( *p++ ) )
      ;
  }

  for ( ; *p != ':' ; ) 
    *d++ = *p++;

  *d = '\0';
}

/*
 *  FormatParagraph
 */

Line_Control *FormatParagraph(
  Line_Control *starting
)
{
  Chain_Node   *insert_after;
  Line_Control *to_add;
  Line_Control *new_line;
  char          Paragraph[ PARAGRAPH_SIZE ];
  int           starting_line;
  char         *src;
  char         *dest;
  boolean       do_loop;
  boolean       is_bullet;
  int           length;
  int           i;

  length = 0;

  starting_line = starting->number;
  insert_after = starting->Node.previous;
  to_add = starting;

  dest = Paragraph;

  is_bullet = FALSE;

  for ( ; ; ) {
    src = to_add->Contents;

    for ( ; *src && isspace( *src ) ; src++ ); /* skip leading spaces */

    if ( !strncmp( src, "@table", 6 ) ) {

      to_add = DeleteLine( to_add );
      src = to_add->Contents;
      for ( i=4 ; *src && isspace( *src ) && i-- ; src++ );
         /* skip leading spaces */

      for ( ; ; ) {
        if ( _Chain_Is_last( &to_add->Node ) ) {
          fprintf(
             stderr,
             "Missing end table for table starting at line %d\n",
             starting_line
           );
           exit_application( 1 );
        }
        if ( !strncmp( src, "@end table", 10 ) ) {
          to_add = DeleteLine( to_add );  
          return to_add;
        } 
        strcpy( Paragraph, src );
        strcpy( to_add->Contents, Paragraph );
        SetLineFormat( to_add, PARAGRAPH_OUTPUT );
        to_add->keyword = RAW_EXAMPLE;
        to_add = (Line_Control *) to_add->Node.next;

        src = to_add->Contents;
        for ( i=4 ; *src && isspace( *src ) && i-- ; src++ );
           /* skip leading spaces */

      }
    }

    if ( *src == '+' ) {
      if ( is_bullet == TRUE )
        break;
      is_bullet = TRUE;
      for ( src++ ; isspace(*src) ; src++ ) ;
    }

    do_loop = TRUE;
    while ( *src && do_loop == TRUE ) {
                    
      if ( isspace( *src ) ) {         /* convert multiple spaces to 1 */

        if ( *(dest-1) == '.' ) {      /* two spaces after period */
          *dest++ = ' ';
          length++;
        }

        assert( length < PARAGRAPH_SIZE );

        *dest++ = ' ';
        length++;

        assert( length < PARAGRAPH_SIZE );
 
        for (  ; isspace( *src ) ; src++ )
          if ( !*src ) {
            do_loop = FALSE;
            break;
          }
 
        continue;
 
      } else {
        *dest++ = *src;
        length++;
        assert( length < PARAGRAPH_SIZE );
        src++;
      }
    }

    to_add = DeleteLine( to_add );

    if ( _Chain_Is_last( &to_add->Node ) )
      break;

    if ( !strlen( to_add->Contents ) )
      break;

    if ( to_add->keyword )
      break;

    if ( !isspace( *(dest-1) ) ) {
      if ( *(dest-1) == '.' ) {
        *dest++ = ' ';
        length++;
        assert( length < PARAGRAPH_SIZE );
      }
      *dest++ = ' ';
      length++;
      assert( length < PARAGRAPH_SIZE );
    }
  }

  new_line = AllocateLine();
  
  SetLineFormat( new_line, (is_bullet) ? BULLET_OUTPUT : PARAGRAPH_OUTPUT );

  *dest = '\0';

  strcpy( new_line->Contents, Paragraph );

  new_line->number = starting_line;
 
  _Chain_Insert( insert_after, &new_line->Node );

  return new_line;
}

/*
 *  FormatMultipleParagraphs
 */

Line_Control *FormatMultipleParagraphs(
  Line_Control *starting
)
{
  Line_Control *line = starting;

  if ( _Chain_Is_last( &line->Node ) ) {
    fprintf( stderr, "end of file reached when expecting text\n" ); 
    exit_application( 0 );
  }

  if ( line->keyword ) {
    line = (Line_Control *) line->Node.next;

    if ( _Chain_Is_last( &line->Node ) ) {
      fprintf( stderr, "end of file reached when expecting text\n" ); 
      exit_application( 0 );
    }
  }
    
  for ( ;; ) {
    assert( line );

    if ( _Chain_Is_last( &line->Node ) )
      break;

    if ( line->keyword )
      break;
    
    if ( !strlen( line->Contents ) ) {
      line = DeleteLine( line );
      continue;
    }

    line = FormatParagraph( line );
    line = (Line_Control *) line->Node.next;
  }
}

/*************************************************************************
 *************************************************************************
 *****              END OF LINE MANIPULATION ROUTINES                *****
 *************************************************************************
 *************************************************************************/

/*
 *  CountKeywordOccurrences
 */

int CountKeywordOccurrences(
  Keyword_indices_t  keyword
)
{
  int           count = 0;
  Line_Control *line;

  for ( line = (Line_Control *) Lines.first ;
        !_Chain_Is_last( &line->Node ) ; 
        line = (Line_Control *) line->Node.next ) {
    if ( line->keyword == keyword )
      count += 1;
  }

  return count;
}

/*
 *  main
 */

int main(
  int    argc,
  char **argv
)
{
  int      c;
  int      index;
  boolean  single_file_mode;

  Verbose = FALSE;
  Statistics = FALSE;
  OutputWord = FALSE;
  IncompletesAreErrors = TRUE;
  InsertTBDs = FALSE;

  while ((c = getopt(argc, argv, "istvw?n:p:u:")) != EOF) {
    switch (c) {
      case 'i':
        IncompletesAreErrors = FALSE;
        break;
      case 't':
        InsertTBDs = TRUE;
        break;
      case 'v':
        Verbose = TRUE;
        break;
      case 'n':
         strcpy( DocsNextNode, optarg );
         break;
      case 'p':
         strcpy( DocsPreviousNode, optarg );
         break;
      case 'u':
         strcpy( DocsUpNode, optarg );
         break;
      case 's':
         Statistics = TRUE;
         break;
      case 'w':
         OutputWord = TRUE;
         break;
      case '?':
        usage();
        return 0;
    }
  }

  if ( Verbose ) {
    fprintf( stderr, "Arguments successfully parsed\n" );
    fprintf( stderr, "Next Node = (%s)\n", DocsNextNode );
    fprintf( stderr, "Previous Node = (%s)\n", DocsPreviousNode );
    fprintf( stderr, "Up Node = (%s)\n", DocsUpNode );
    fprintf( stderr, "Output Format = (%s)\n", (OutputWord) ? "Word" : "Texi" );
  }

  FillLinePool();

  for ( index=optind ; index < argc ; index++ ) {
    ProcessFile( argv[ index ], NULL );
  }
    
  if ( Verbose )
    fprintf( stderr, "Exitting\n" );

  return 0;
}

/*
 *  ProcessFile
 */

void ProcessFile(
  char   *inname,
  char   *outname
)
{
   char            out[ 256 ];
   int             index;
   int             out_index;

   /*
    *  Automatically generate the output file name.
    */

   if ( outname == NULL ) {
     out_index = 0;
     for( index=0 ; inname[index] && inname[index] != '.' ; index++ ) {
/*
       if ( inname[ index ] != '_' )
*/
         out[ out_index++ ] = inname[ index ];
     }

     if ( OutputWord ) {
       out[ out_index++ ] = '.';
       out[ out_index++ ] = 't';
       out[ out_index++ ] = 'x';
       out[ out_index++ ] = 't';
     } else {
       out[ out_index++ ] = '.';
       out[ out_index++ ] = 't';
       out[ out_index++ ] = 'e';
       out[ out_index++ ] = 'x';
       out[ out_index++ ] = 'i';
     }
     out[ out_index ] = '\0';

   }

   /*
    *  Read the file into our internal data structure
    */

   if ( Verbose )
     printf( "Processing (%s) -> (%s)\n", inname, out );

   ReadFileIntoChain( inname );

   if ( Verbose )
     fprintf( stderr, "-------->FILE READ IN\n" );

   /*
    *  Remove any spaces before the keyword and mark each keyword line as
    *  such.  Also remove extra white space at the end of lines.
    */

   StripBlanks();

   if ( Verbose )
     fprintf( stderr, "-------->BLANKS BEFORE KEYWORDS STRIPPED\n" );

   /*
    *  Count the number of each type of thing
    */

   NumberOfAttributes    = CountKeywordOccurrences( ATTRIBUTE );
   NumberOfAssociations  = CountKeywordOccurrences( ASSOCIATION );
   NumberOfAbstractTypes = CountKeywordOccurrences( ABSTRACT_TYPE );
   NumberOfDataItems     = CountKeywordOccurrences( DATA_ITEM );
   NumberOfMethods       = CountKeywordOccurrences( METHOD );
   NumberOfTasks         = CountKeywordOccurrences( TASK );

   if ( Verbose || Statistics ) {
     fprintf( stderr, "NUMBER OF ATTRIBUTES = %d\n", NumberOfAttributes );
     fprintf( stderr, "NUMBER OF ASSOCIATIONS = %d\n", NumberOfAssociations );
     fprintf( stderr, "NUMBER OF ABSTRACT TYPES = %d\n", NumberOfAbstractTypes);
     fprintf( stderr, "NUMBER OF DATA ITEMS = %d\n", NumberOfDataItems );
     fprintf( stderr, "NUMBER OF METHODS = %d\n", NumberOfMethods );
     fprintf( stderr, "NUMBER OF TASKS = %d\n", NumberOfTasks );
   }

   /*
    *  1.  Merge paragraphs entered as multiple lines into a single node 
    *      on the chain.
    *  2.  Clean up "colon lines".
    */

   if ( MergeText() == -1 )
     exit_application( 1 );

   if ( Verbose )
     fprintf( stderr, "-------->PARAGRAPHS MERGED\n" );

   RemovePagebreaks();
   
   /*
    *  Remove extraneous white space
    */

   if ( Verbose )
     fprintf( stderr, "-------->PAGEBREAKS REMOVED\n" );

   /*
    *  At this point, the only unmarked lines should be empty or errors.
    *  This deletes empty unmarked lines and flags the others as errors.
    */

   if ( RemoveExtraBlankLines() == -1 )
     exit_application( 1 );
 
   if ( Verbose )
     fprintf( stderr, "-------->EXTRA BLANK LINES REMOVED\n" );

   /*
    *  Now report which lines do not appear to be complete
    */

   if ( CheckForIncomplete() == -1 )
     exit_application( 1 );

   if ( Verbose )
     fprintf( stderr, "-------->CHECKED FOR INCOMPLETE KEYWORDS\n" );

   /*
    *  Check that all the required sections are present.
    */

   if ( CheckOutline() == -1 )
     exit_application( 1 );
  
   if ( Verbose )
     fprintf( stderr, "-------->CHECKED FOR HIGH LEVEL SECTION PROBLEMS\n" );

   /*
    *  Check for problems in each section.
    */

   if ( CheckSections() == -1 )
     exit_application( 1 );

   if ( Verbose )
     fprintf( stderr, "-------->CHECKED FOR INTERNAL SECTION PROBLEMS\n" );

   /*
    *  Look for Internal Consistencies
    */

   if ( Verbose ) 
     fprintf( stderr, "-------->LOOKING FOR ERRORS\n" );

   LookForInternalInconsistencies();

   /*
    *  Formatting the file 
    */

   if ( OutputWord ) {
     FormatToWord();
     if ( Verbose ) 
       fprintf( stderr, "-------->FILE FORMATTED TO WORD\n" );
   } else {
     FormatToTexinfo();
     if ( Verbose ) 
       fprintf( stderr, "-------->FILE FORMATTED TO TEXINFO\n" );
   }

   /*
    *  Print the file 
    */

   PrintFile( out );

   if ( Verbose ) 
     fprintf( stderr, "-------->FILE PRINTED\n" );

   /*
    *  Clean Up 
    */

   ReleaseFile();

   if ( Verbose )
     fprintf( stderr, "-------->FILE RELEASED\n" );
}

/*
 *  LookForInternalInconsistencies
 *
 *  This routine looks for lines with no formatting information and
 *  lines with too much formatting information.
 */

void LookForInternalInconsistencies( void )
{
  Line_Control  *line;
  int            i;
  int            errors = 0;
  char          *src;

  for ( line = (Line_Control *) Lines.first ;
        !_Chain_Is_last( &line->Node ) ;
        line = (Line_Control *)line->Node.next
        ) {
    if ( !line->keyword && line->format == NO_EXTRA_FORMATTING_INFO ) {
      fprintf(
        stderr,
        "\nLine %d has no keyword or formatting:\n", 
        line->number
      );
      PrintSurroundingLines( line, 3,  3 );
      errors++;
    } 
  }
 
  if ( errors ) 
    exit_application( 1 );
}

/*
 *  usage
 */

void usage( void )
{
  int index;

  for ( index=0 ; strcmp( Usage_Strings[ index ], "EOF" ) ; index++ )
    fprintf( stderr, Usage_Strings[ index ] );
}

/*
 *  ReadFileIntoChain
 */

void ReadFileIntoChain( 
  char *inname
)
{
   FILE *InFile;
   int   line_count;
   int   max_length;
   char *line;
   char  Buffer[ BUFFER_SIZE ];
   Line_Control *new_line;

   InFile = fopen( inname, "r" );

   if ( !InFile ) {
     fprintf( stderr, "Unable to open (%s)\n", inname );
     exit( 1 );
   }
   assert( InFile );

   max_length = 0;
   line_count = 0;

   _Chain_Initialize_empty( &Lines );

   for ( ;; ) {
      line = fgets( Buffer, BUFFER_SIZE, InFile );
      if ( !line ) 
        break;

      Buffer[ strlen( Buffer ) - 1 ] = '\0';

      new_line = AllocateLine();
 
      strcpy( new_line->Contents, Buffer );

      new_line->number = ++line_count;
 
      _Chain_Append( &Lines, &new_line->Node );
   } 

   fclose( InFile ); 
}

/*
 *  StripBlanks
 */

void StripBlanks( void )
{
  Line_Control      *line;
  Keyword_indices_t  index;
  int                indentation;
  int                length;
  Keyword_info_t    *key;
 
  for ( line = (Line_Control *) Lines.first ;
        !_Chain_Is_last( &line->Node ) ;
        line = (Line_Control *) line->Node.next
        ) {

    /*
     *  Strip white space from the end of each line 
     */

    length = strlen( line->Contents );

    while ( isspace( line->Contents[ --length ] ) ) 
      line->Contents[ length ] = '\0';

    /*
     *  Strip white space from the front of each keyword line.
     */

    for ( index=KEYWORD_FIRST ; index <= KEYWORD_LAST ; index++ ) {
      key = &Keywords[ index ];

      switch ( key->level ) {
        case SECTION:    indentation = 0; break; 
        case SUBSECTION: indentation = 0; break; 
        case HEADING:    indentation = 2; break; 
        default:         assert( FALSE );
      }

      if ( !strncmp( &line->Contents[ indentation ], key->Name, 
                    strlen( key->Name ) ) ) {
        if ( indentation )
          strcpy( line->Contents,  &line->Contents[ indentation ] );
        line->keyword = index;
        break;
      }
    }
  }
}

/*
 *  CrunchRight
 */

boolean CrunchRight(
  Line_Control *line
)
{
  char  Buffer[ PARAGRAPH_SIZE ];
  char *src;
  char *dest;
  boolean is_text_to_right = FALSE;

  src  = line->Contents;
  dest = Buffer;

  while( *src != ':' ) {
    if ( !*src ) {
      fprintf(
        stderr,
        "Line %d should have had a colon\n",
        line->number
      );
      exit_application( 1 );
    }
    *dest++ = *src++;
  }
 
  *dest++ = *src++;    /* this is the ':' */
 
  if ( *src ) {
    *dest++ = ' ';
    while ( *src && isspace( *src ) )
      src++;
 
    if ( *src )
      is_text_to_right = TRUE;
 
    while ( *src )
      *dest++ = *src++;
  }
 
  *dest = '\0';

  strcpy( line->Contents, Buffer );

  return is_text_to_right;
}

/*
 *  CrunchBelow
 */

void CrunchBelow(
  Line_Control          *start_line,
  Keywords_text_below_t  text_below_mode
)
{
  Line_Control      *line;
  int                i;
  int                leading_white;
  int                length;
 
  switch ( text_below_mode ) {
    case BL_FORBIDDEN:
      assert( FALSE );
    case BL_FORMATTED:
      (void) FormatMultipleParagraphs( start_line );
      break;
    case BL_RAW:
      for ( line = (Line_Control *) start_line->Node.next;
            !_Chain_Is_last( &line->Node ) ;
          ) {
         if ( strlen( line->Contents ) )
           break;
         line = DeleteLine( line );
      }  

      for ( i=0 ; isspace( line->Contents[ i ] ) ; i++ )
        ;
      
      leading_white = i;

      for ( ;
            !_Chain_Is_last( &line->Node ) ;
            ) {
 
         assert( line );

         if ( line->keyword )
           break;

         assert( !_Chain_Is_last( &line->Node ) );

         length = strlen( line->Contents );

         if ( length ) {
           if ( length < leading_white ) {
             fprintf(
               stderr,
               "\nLine %d %s:\n",
               line->number,
               "has inconsistent spacing -- is too short -- see 1st line of text"
             );
             PrintSurroundingLines( line, 0,  0 );
             fprintf( stderr, "\n" );

           } 
           for ( i=0 ; i < leading_white ; i++ ) {
             if ( !isspace( line->Contents[ i ] ) ) {
               fprintf(
                 stderr,
                 "\nLine %d %s:\n",
                 line->number,
                 "has inconsistent spacing -- see 1st line of text"
               );
               PrintSurroundingLines( line, 0,  0 );
               fprintf( stderr, "\n" );
               break;
             }
           } 

           strcpy( line->Contents, &line->Contents[ leading_white ] );

           SetLineFormat( line, RAW_OUTPUT );
           line = (Line_Control *) line->Node.next;
         } else {
           SetLineFormat( line, RAW_OUTPUT );
           /* line = DeleteLine( line ); */
           line = (Line_Control *) line->Node.next;
         }
      }
      break;
  }
}

/*
 *  MergeText
 */
 
/* XXX expand this and address the error numbers */
char *Format_Errors[] = {
  /* unused */             "no formatting error",
  /* RT_NO_CHECK      */   "why am I here",
  /* RT_FORBIDDEN     */   "no text allowed to right or below",
  /* RT_OPTIONAL      */   "text to right optional -- none below",
  /* RT_NONE          */   "text to right is \"none\" or nothing -- none below",
  /* RT_REQUIRED      */   "text to right required -- none below",
  /* RT_BELOW         */   "text to right forbidden -- text below required",
  /* RT_NONE_OR_BELOW */   "text to right is \"none\" OR there is text below",
  /* RT_MAYBE_BELOW   */   "text to right required -- text below optional",
  /* RT_EITHER        */   "text to right OR below",
  /* RT_BOTH          */   "text to right AND below"
};


int MergeText( void )
{
  Line_Control      *line;
  Line_Control      *next;
  Line_Control      *new_line;
  Keyword_info_t    *key;
  boolean            is_text_to_right;
  boolean            is_text_below;
  boolean            add_text_to_right;
  boolean            add_text_below;
  int                errors = 0;
  int                error_code = 0;
 
  for ( line = (Line_Control *) Lines.first ;
        !_Chain_Is_last( &line->Node ) ;
        ) {
 
    if ( error_code ) {

      fprintf(
        stderr,
        "\nLine %d %s:\n",
        line->number,
        Format_Errors[ error_code ]
      );
      PrintSurroundingLines( line, 0,  3 );
      fprintf( stderr, "\n" );
      error_code = 0;
      line = (Line_Control *) line->Node.next;
    }

    key = &Keywords[ line->keyword ];

    if ( !line->keyword || key->text_mode == RT_NO_CHECK ) {
      line = (Line_Control *) line->Node.next;
      continue;
    }

    /*
     *  Figure out where the text is for this keyword.  It is a pretty
     *  ugly thing to look ahead for text below because of intermediate
     *  blank lines which we are not allowed to remove.
     */

    add_text_to_right = FALSE;
    add_text_below = FALSE;

    is_text_to_right = CrunchRight( line );

    is_text_below = FALSE;

    for ( next = (Line_Control *) line->Node.next ; 
          !_Chain_Is_last( &next->Node ) ;
          next = (Line_Control *) next->Node.next ) {

      if ( next->keyword )
        break;

      if ( strlen( next->Contents ) ) {
        is_text_below = TRUE;
        break;
      }
    }

    switch ( key->text_mode ) {
      case RT_NO_CHECK:  /* no requirements */
        break;

      case RT_FORBIDDEN: /* no text to right or below allowed */
        if ( is_text_to_right || is_text_below ) {
          error_code = 1;
          errors++;
          continue;
        }
        break;

      case RT_OPTIONAL:  /* text to right optional -- none below */
        if ( is_text_below ) {
          error_code = 2;
          errors++;
          continue;
        }
        break;

      case RT_NONE:      /* text to right is "none" or nothing -- none below */
        if ( (is_text_to_right && !strstr( line->Contents, "none" )) ||
             is_text_below ) {
          error_code = 3;
          errors++;
          continue;
        }
        break;

      case RT_REQUIRED:  /* text to right required -- none below */
        if ( is_text_below ) {
          error_code = 4;
          errors++;
          continue;
        }

        if ( !is_text_to_right ) {
          if ( !InsertTBDs ) {
            error_code = 4;
            errors++;
            continue;
          } else
            add_text_to_right = TRUE;
        }
        break;

      case RT_BELOW:     /* text to right forbidden -- text below required */
        if ( is_text_to_right ) {
          error_code = 5;
          errors++;
          continue;
        }

        if ( !is_text_below ) {
          if ( !InsertTBDs ) {
            error_code = 5;
            errors++;
            continue;
          } else
            add_text_below = TRUE;
        }
        break;

      case RT_NONE_OR_BELOW: /* text to right is "none" OR text below */

        if ( is_text_to_right ) {
          if ( strstr( line->Contents, "none" ) )
            break;
          error_code = 6;
          errors++;
          continue;
        }

        if ( !is_text_below ) {
          if ( !InsertTBDs ) {
            error_code = 6;
            errors++;
            continue;
          } else
            add_text_to_right = TRUE;
        }
        break;
                          /* text to right required -- text below optional */
      case RT_MAYBE_BELOW:
        if ( !is_text_to_right ) {
          error_code = 7;
          errors++;
          continue;
        }
        break;

      case RT_EITHER:    /* text to right OR below */
        if ( !(is_text_to_right || is_text_below) ) {
          if ( !InsertTBDs ) {
            error_code = 8;
            errors++;
            continue;
          } else
            add_text_to_right = TRUE;
        }
        break;

      case RT_BOTH:      /* text to right AND below */
        if ( !(is_text_to_right && is_text_below) ) {
          if ( !InsertTBDs ) {
            error_code = 9;
            errors++;
            continue;
          } else {
            add_text_to_right = TRUE;
            add_text_below = TRUE;
          }
        }
        break;
    }

    if ( add_text_to_right )
      strcat( line->Contents, " TBD" );

    if ( add_text_below ) {
      new_line = AllocateLine();
      strcpy( new_line->Contents, "TBD" );
      _Chain_Insert( &line->Node, &new_line->Node );
      is_text_below = TRUE;
    }

    if ( is_text_below )
      CrunchBelow( line, key->text_below_mode );

    line = (Line_Control *) line->Node.next;
  }
  return (errors) ? -1 : 0;
}

/*
 *  CheckForIncomplete
 */

char *IncompleteMarkers[] = {
  "??",
  "xxx",
  "XXX",
  "xyz",
  "XYZ"
};

int CheckForIncomplete()
{
  Line_Control *line;
  int           i;
  int           errors = 0;

  for ( line = (Line_Control *) Lines.first ;
        !_Chain_Is_last( &line->Node ) ; 
        line = (Line_Control *) line->Node.next
        ) {
    for ( i=0 ; i < NUMBER_ELEMENTS( IncompleteMarkers ) ; i++ ) {
      if ( !strstr( line->Contents, IncompleteMarkers[ i ] ) ) 
        continue;

      fprintf(
        stderr,
        "\nLine %d %s:\n",
        line->number,
        "appears to be incomplete"
      );
      PrintSurroundingLines( line, 0,  0 );
      fprintf( stderr, "\n" );
      if ( IncompletesAreErrors )
        errors++;
      break;
    }
  }
  return ( errors ) ? -1 : 0;
}

/*
 *  CheckOutline
 *
 *  Insure all section headers are present.
 *  Check sections which say "none", really have subsections.
 *  Check sections which have subsections, don't say none.
 */

int CheckOutline( void )
{
  Line_Control      *line;
  int                count;
  int                errors = 0;
  void              *none_present;
  boolean            KeywordsPresent[ KEYWORD_LAST + 1 ];
  Keyword_indices_t  keyword;

  for ( keyword=0 ; keyword <= KEYWORD_LAST ; keyword++ )
    KeywordsPresent[ keyword ] = FALSE;

  for ( line = (Line_Control *) Lines.first ;
        !_Chain_Is_last( &line->Node ) ; 
        line = (Line_Control *) line->Node.next
        ) {

    KeywordsPresent[ line->keyword ] = TRUE;

    switch ( line->keyword ) {
      case ATTRIBUTE_DESCRIPTIONS:
        count = NumberOfAttributes;
        break;
      case ASSOCIATION_DESCRIPTIONS:
        count = NumberOfAssociations;
        break;
      case ABSTRACT_TYPE_DESCRIPTIONS:
        count = NumberOfAbstractTypes;
        break;
      case DATA_ITEM_DESCRIPTIONS:
        count = NumberOfDataItems;
        break;
      case METHOD_DESCRIPTIONS:
        count = NumberOfMethods;
        break;
      case TASK_DESCRIPTIONS:
        count = NumberOfTasks;
        break;
      default:
        count = -1;
        break;
    }
    if ( count == -1 )
      continue;

    none_present = strstr( line->Contents, "none" );

    /* valid cases are (none_present && !count) AND (!none_present && count) */
    if ( none_present && count ) {
      fprintf(
        stderr,
        "\nLine %d : %s\n",
        line->number,
        "section header says \"none\" and there are subsections"
      );
      PrintSurroundingLines( line, 0,  0 );
      fprintf( stderr, "\n" );
      errors++;
    }
    if ( !none_present && !count ) {
      fprintf(
        stderr,
        "\nLine %d : %s\n",
        line->number,
        "section header does not say \"none\" and there are no subsections"
      );
      PrintSurroundingLines( line, 0,  0 );
      fprintf( stderr, "\n" );
      errors++;
    }
  }

  for ( keyword=0 ; keyword <= KEYWORD_LAST ; keyword++ ) {
    if ( Keywords[ keyword ].level != SECTION )
      continue;

    if ( !KeywordsPresent[ keyword ] ) {
      fprintf(
        stderr,
        "Section (%s) is missing\n",
        Keywords[ keyword ].Name
      );
      errors++;
    }
  }

  return (errors) ? -1 : 0;
}

/*
 *  ReportMissingKeywords
 */

int ReportMissingKeywords(
  Section_info_t  *section,
  Line_Control    *line,
  int              elements_present[ MAXIMUM_ELEMENTS ]
)
{
  int       i;
  int       errors = 0;
  int       last_found;

#ifdef SECTION_DEBUG
  PrintLine( line );
  for ( i=0 ; i < MAXIMUM_ELEMENTS ; i++ )
    fprintf( stderr, "%d ", elements_present[ i ] );
  fprintf( stderr, "\n" );
#endif
  
  /*
   *  Check for missing sections
   */

  for ( i=0 ; i < MAXIMUM_ELEMENTS ; i++ ) {
    if ( section->Description[ i ].keyword == UNUSED )
      break;

#ifdef SECTION_DEBUG
    fprintf(
      stderr, 
      "%d %d %s\n",
      section->Description[ i ].is_required,
      elements_present[ i ],
      Keywords[ section->Description[ i ].keyword ].Name
    );
#endif

    if ( section->Description[ i ].is_required && elements_present[i] == -1 ) {
      fprintf( 
        stderr,
        "Section starting at line %d is missing the %s keyword\n",
        line->number,
        Keywords[ section->Description[ i ].keyword ].Name
      );
      errors++;
    }
  }

  last_found = -1;

  for ( i=0 ; i < MAXIMUM_ELEMENTS ; i++ ) {
    if ( section->Description[ i ].keyword == UNUSED )
      break;

    if ( elements_present[i] == -1 )
      continue;

    if ( elements_present[i] > last_found ) {
      last_found = elements_present[i];
      continue;
    }

    fprintf(
      stderr,
      "Keywords in the section starting at line %d are in the wrong order\n",
      line->number
    );
    errors++;
    break;

  }

  return errors;
}

/*
 *  ScanForNextSection
 */

Line_Control *ScanForNextSection(
  Line_Control   *start_line,
  Section_info_t *section
)
{
  Line_Control   *line;
  Element_info_t *Elements;

  line = start_line;
  Elements = section->Description;

  for ( ;; ) {
    line = (Line_Control *) line->Node.next;

    if ( line->keyword == END_OBJECT ) 
      break;

    assert( line );
    assert( !_Chain_Is_last( &line->Node ) );

    if ( Keywords[ line->keyword ].level == SECTION && 
         section->Next_section != line->keyword ) {

        fprintf( 
          stderr,
          "Sections appear to be out of order at line %d\n",
          start_line->number
        );
        return NULL;
    }

    if ( section->Next_section == line->keyword )   /* next section */
      break;

    if ( Elements[ 0 ].keyword == line->keyword )   /* repeating subsection */
      break;
  }

  return line;
}

/*
 *  CheckSections
 */

int CheckSections( void )
{
  Line_Control   *line;
  Line_Control   *scan_line;
  Section_info_t *section;
  Keyword_info_t *key;
  Element_info_t *Elements;
  int             elements_present[ MAXIMUM_ELEMENTS ];
  boolean         stay_in_subsection;
  int             i;
  int             errors;
  int             subsection;
  int             element;
  int             keyword_count;
  Line_Control   *next_section;

  errors = 0;
  line = (Line_Control *) Lines.first;

  for ( i=0 ; i < NUMBER_ELEMENTS( Sections ) ; i++ ) {
    section = Sections[ i ];
    Elements = section->Description;
    subsection = 0;

    if ( strstr( line->Contents, "none" ) ) {
      next_section = ScanForNextSection( line, section );
      if ( !next_section ) {
        errors++;
        goto end_object;
      }
      line = next_section;
      if ( line->keyword == END_OBJECT )
        goto end_object;
      continue;
    }

    while ( Elements[ 0 ].keyword != line->keyword ) {
      if ( line->keyword == END_OBJECT )
        goto end_object;

      if( !line || _Chain_Is_last( &line->Node ) ) {
        fprintf( stderr, "out of sync -- aborting\n" );
        errors++;
        goto end_object;
      }
      line = (Line_Control *) line->Node.next;
    }

repeating_subsection:

#ifdef SECTION_DEBUG
    fprintf( stderr, "Checking Section %d Subsection %d\n", i, subsection );
#endif
    assert( line );
    assert( !_Chain_Is_last( &line->Node ) );
  
    /*
     *  Per Subsection Initialization
     */

    subsection++;

    keyword_count = 0;

    next_section = ScanForNextSection( line, section );
    if ( !next_section ) {
      errors++;
      goto end_object;
    }

    for ( element=0 ; element < MAXIMUM_ELEMENTS ; element++ )
      elements_present[ element ] = -1;

    /*  
     *  Do a subsection
     */

    /*  validate each keyword */

    for ( scan_line = line ; ; ) {

      if ( !scan_line->keyword ) {
        scan_line = (Line_Control *) scan_line->Node.next;
        continue;
      }

      if ( scan_line == next_section )
        break;

      if ( *Keywords[ scan_line->keyword ].keyword_validation_routine ) {
        if ( (*Keywords[ scan_line->keyword ].keyword_validation_routine )(
               scan_line
              ) ) errors++;
      }

      scan_line = (Line_Control *) scan_line->Node.next;
    }

    /* scan subsection for keywords */

    for ( scan_line = line ; ; ) {

      if ( !scan_line->keyword ) {
        scan_line = (Line_Control *) scan_line->Node.next;
        continue;
      }

      if ( scan_line == next_section )
        break;

      for ( element=0 ; element < MAXIMUM_ELEMENTS ; element++ ) {
        if ( scan_line->keyword == Elements[ element ].keyword ) {
          if ( elements_present[ element ] != -1 ) {
            fprintf( 
              stderr,
              "Section starting at line %d has the %s keyword more than once\n",
              line->number,
              Keywords[ Elements[ i ].keyword ].Name
            );
            errors++;
          }
          
#ifdef SECTION_DEBUG
          fprintf( stderr, "FOUND %s %d %d\n", 
            Keywords[ Elements[ element ].keyword ].Name, 
            element, keyword_count ); 
#endif
          elements_present[ element ] = keyword_count++;
          break;
        }
        if ( Elements[ element ].keyword == UNUSED )
          break;
      }

      scan_line = (Line_Control *) scan_line->Node.next;
    }
    errors += ReportMissingKeywords( section, line, elements_present );

    /*
     *  Validate the section as a whole
     */
     
    if ( section->section_validation_routine ) {
      if ( (*section->section_validation_routine)(section, line, next_section) )
        errors++;
    }

scan_for_next_section:
    /*
     *  Scan forward until next subsection or next section
     */

#ifdef SECTION_DEBUG
    fprintf( stderr, "End of Subsection\n" );
#endif
    line = next_section;
    if ( line->keyword == END_OBJECT ) {
      goto end_object;
    }

    if ( Elements[ 0 ].keyword == line->keyword ) { /* repeating subsection */
      if ( !section->repeats ) {
        fprintf(
          stderr,
          "Invalid repeating subsection starting at line %d\n",
          line->number
        );
        errors++;
        goto end_object;
      }
      goto repeating_subsection;
    }
 
  }

end_object:
  return (errors) ? -1 : 0;
}
/*
 *  RemovePagebreaks
 */

void RemovePagebreaks()
{
  Line_Control *line;

  for ( line = (Line_Control *) Lines.first ;
        !_Chain_Is_last( &line->Node ) ; 
        ) {
    if ( !strcmp( line->Contents, PAGE_SEPARATOR ) ) 
        line = DeleteLine( line );
    else 
      line = (Line_Control *) line->Node.next;
  }
}

/*
 *  strIsAllSpace
 */

boolean strIsAllSpace(
  char *s
)
{
  char *p;

  for ( p = s ; *p ; p++ )
    if ( !isspace( *p ) )
      return FALSE;

  return TRUE;
}

/*
 *  RemoveExtraBlankLines
 *
 *  NOTE:  Be careful not remove to remove white space in raw text.
 */

int RemoveExtraBlankLines()
{
  Line_Control *line;
  Line_Control *previous;
  int errors;

  errors = 0;

  for ( line = (Line_Control *) Lines.first ;
        !_Chain_Is_last( &line->Node ) ; 
        line = (Line_Control *) line->Node.next
        ) {
    if ( line->keyword || line->format )
      continue;

    if ( !strlen( line->Contents ) ) {
      line = DeleteLine( line );
      line = (Line_Control *) line->Node.previous;
      continue;
    }

    fprintf(
      stderr,
      "\nLine %d is not associated with a keyword:\n", 
      line->number
    );
    PrintSurroundingLines( line, 0,  0 );
    fprintf( stderr, "\n" );
    errors++;
  }
  return ( errors ) ? -1 : 0;
}

/*
 *  strCopyToColon
 */

void strCopyToColon(
  char *src,
  char *dest
)
{
  char *s = src;
  char *d = dest;

  for ( ; *s && *s != ':' ; ) 
    *d++ = *s++;
  *d ='\0';
}

/*
 *  BuildTexinfoNodes
 */

void BuildTexinfoNodes( void ) 
{
  Line_Control *line;
  Line_Control *new_line;
  Line_Control *next_node;
  char          Buffer[ BUFFER_SIZE ];
  char          ObjectName[ BUFFER_SIZE ];
  char          NodeName[ BUFFER_SIZE ];
  char          NextNode[ BUFFER_SIZE ];
  char          NextNodeName[ BUFFER_SIZE ];
  char          PreviousNodeName[ BUFFER_SIZE ];
  char          UpNodeName[ BUFFER_SIZE ];
  char          SectionName[ BUFFER_SIZE ];
  char          MenuBuffer[ BUFFER_SIZE ];
  Line_Control *menu_insert_point;
  Line_Control *node_line;
  boolean       next_found;
  int           menu_items;

  strcpy( PreviousNodeName, DocsPreviousNode );

  for ( line = (Line_Control *) Lines.first ;
        !_Chain_Is_last( &line->Node ) ; 
        line = (Line_Control *) line->Node.next
      ) {
    menu_insert_point = (Line_Control *) line->Node.next;

    switch ( Keywords[ line->keyword ].level ) {
      case TEXT:
      case HEADING:
        break;
      case SECTION:
        if ( line->keyword == END_OBJECT )
          goto bottom; 

        if ( line->keyword == OBJECT ) {
          LineCopyFromRight( line, Buffer );
          sprintf( ObjectName, "%s Object", Buffer );
          strcpy( NodeName, ObjectName );
          strcpy( UpNodeName, DocsUpNode );
        } else {
          LineCopySectionName( line, Buffer );
          sprintf( NodeName, "%s %s", ObjectName, Buffer );
          strcpy( UpNodeName, ObjectName );
        }
        strtoInitialCaps( NULL, NodeName );
        strtoInitialCaps( NULL, UpNodeName );
        strcpy( SectionName, NodeName );

        /*
         *  Go ahead and put it on the chain in the right order (ahead of
         *  the menu) and we can fill it in later (after the menu is built).
         */

        new_line = AllocateLine();
        strcpy( new_line->Contents, "@ifinfo" );
        _Chain_Insert( line->Node.previous, &new_line->Node );

        node_line = AllocateLine();
        _Chain_Insert( line->Node.previous, &node_line->Node );

        new_line = AllocateLine();
        strcpy( new_line->Contents, "@end ifinfo" );
        _Chain_Insert( line->Node.previous, &new_line->Node );

        menu_items = 0;
       
        if ( line->keyword == OBJECT ) {
          next_node = (Line_Control *) line->Node.next;
          next_found = FALSE;
          for ( ; ; ) {
            if ( next_node->keyword == END_OBJECT )
              break;
            if ( Keywords[ next_node->keyword ].level == SECTION ) {
              LineCopySectionName( next_node, Buffer );
              strtoInitialCaps( NULL, Buffer );     

              if ( !next_found ) {
                next_found = TRUE;
                sprintf( NextNodeName, "%s %s", ObjectName, Buffer );
              }
              if ( menu_items == 0 ) {
                new_line = AllocateLine();
                strcpy( new_line->Contents, "@ifinfo" );
                _Chain_Insert( menu_insert_point->Node.previous, &new_line->Node );
 
                new_line = AllocateLine();
                strcpy( new_line->Contents, "@menu" );
                _Chain_Insert( menu_insert_point->Node.previous, &new_line->Node );
              }

              menu_items++;

              new_line = AllocateLine();
              sprintf( new_line->Contents, "* %s %s::", ObjectName, Buffer );
              _Chain_Insert( menu_insert_point->Node.previous, &new_line->Node);
            }
            next_node = (Line_Control *) next_node->Node.next;
          }
        } else {
          next_node = (Line_Control *) line->Node.next;
        
          next_found = FALSE;
          for ( ; ; ) {
            if ( Keywords[ next_node->keyword ].level == SECTION ) {
              if ( !next_found ) {
                if ( next_node->keyword == END_OBJECT ) {
                  strcpy( NextNodeName, DocsNextNode );
                } else {
                  LineCopySectionName( next_node, Buffer );
                  sprintf( NextNodeName, "%s %s", ObjectName, Buffer );
                  strtoInitialCaps( NULL, NextNodeName );
                }
                next_found = TRUE;
              }
              break;
            } else if ( Keywords[ next_node->keyword ].level == SUBSECTION ) {
              LineCopySectionName( next_node, Buffer );
              strtoInitialCaps( NULL, Buffer );
              sprintf( MenuBuffer, "%s %s - ", ObjectName, Buffer );
              LineCopyFromRight( next_node, Buffer );
              strcat( MenuBuffer, Buffer );

              if ( menu_items == 0 ) {
                new_line = AllocateLine();
                strcpy( new_line->Contents, "@ifinfo" );
                _Chain_Insert( menu_insert_point->Node.previous, &new_line->Node );
 
                new_line = AllocateLine();
                strcpy( new_line->Contents, "@menu" );
                _Chain_Insert( menu_insert_point->Node.previous, &new_line->Node );
              }

              menu_items++;

              new_line = AllocateLine();
              sprintf( new_line->Contents, "* %s::", MenuBuffer );
              _Chain_Insert( menu_insert_point->Node.previous, &new_line->Node );

              if ( !next_found ) {
                next_found = TRUE;
                strcpy( NextNodeName, MenuBuffer );
              }
            }
            next_node = (Line_Control *) next_node->Node.next;
          }
        }

        if ( menu_items ) {
          new_line = AllocateLine();
          strcpy( new_line->Contents, "@end menu" );
          _Chain_Insert( menu_insert_point->Node.previous, &new_line->Node );
 
          new_line = AllocateLine();
          strcpy( new_line->Contents, "@end ifinfo" );
          _Chain_Insert( menu_insert_point->Node.previous, &new_line->Node );
        }

#if 0
        fprintf(
          stderr,
          "@node %s, %s, %s, %s\n",
          NodeName,
          NextNodeName,
          PreviousNodeName,
          UpNodeName
        );
#endif
        /* node_line was previously inserted */
        sprintf(
          node_line->Contents,
          "@node %s, %s, %s, %s",
          NodeName,
          NextNodeName,
          PreviousNodeName,
          UpNodeName
        );

        strcpy( PreviousNodeName, NodeName );
        break;

      case SUBSECTION:
        strcpy( UpNodeName, SectionName );

        LineCopySectionName( line, Buffer );
        strtoInitialCaps( NULL, Buffer );
        sprintf( NodeName, "%s %s - ", ObjectName, Buffer );
        LineCopyFromRight( line, Buffer );
        strcat( NodeName, Buffer );

        new_line = AllocateLine();
        strcpy( new_line->Contents, "@ifinfo" );
        _Chain_Insert( line->Node.previous, &new_line->Node );

        next_node = (Line_Control *) line->Node.next;
        for ( ; ; ) {
          if ( Keywords[ next_node->keyword ].level == SECTION ) {
            if ( next_node->keyword == END_OBJECT ) {
              strcpy( NextNodeName, DocsNextNode );
            } else {
              LineCopySectionName( next_node, Buffer );
              sprintf( NextNodeName, "%s %s", ObjectName, Buffer );
              strtoInitialCaps( NULL, NextNodeName );
            }
            break;
          } else if ( Keywords[ next_node->keyword ].level == SUBSECTION ) {
            LineCopySectionName( next_node, Buffer );
            strtoInitialCaps( NULL, Buffer );
            sprintf( NextNodeName, "%s %s - ", ObjectName, Buffer );
            LineCopyFromRight( next_node, Buffer );
            strcat( NextNodeName, Buffer );
            break;
          }
          next_node = (Line_Control *) next_node->Node.next;
        }

#if 0
        fprintf(
          stderr,
          "@node %s, %s, %s, %s\n",
          NodeName,
          NextNodeName,
          PreviousNodeName,
          UpNodeName
        );
#endif
        new_line = AllocateLine();
        sprintf(
          new_line->Contents,
          "@node %s, %s, %s, %s",
          NodeName,
          NextNodeName,
          PreviousNodeName,
          UpNodeName
        );
        _Chain_Insert( line->Node.previous, &new_line->Node );

        new_line = AllocateLine();
        strcpy( new_line->Contents, "@end ifinfo" );
        _Chain_Insert( line->Node.previous, &new_line->Node );

        strcpy( PreviousNodeName, NodeName );
        break;
    }
  }
bottom:
}

/*
 *  FormatToTexinfo
 */

char *Texinfo_Headers[] = {
  "\\input texinfo   @c -*-texinfo-*-",
  "@c %**start of header",
  "@setfilename ",
  "@settitle ",
  "@paragraphindent 0",
  "@c %**end of header",
  "",
  "@c",
  "@c  COPYRIGHT (c) 1996.",
  "@c  On-Line Applications Research Corporation (OAR).",
  "@c  All rights reserved.",
  "@c",
  "@c  This file is automatically generated.  DO NOT EDIT!!",
  "@c",
  "",
  "@c  This prevents a black box from being printed on overfull lines.",
  "@c  The alternative is to rework a sentence to avoid this problem.",
  "@finalout",
  "",
  "@tex",
  "\\global\\parindent 0in",
  "\\global\\chapheadingskip = 15pt plus 4pt minus 2pt",
  "\\global\\secheadingskip = 12pt plus 4pt minus 2pt",
  "\\global\\subsecheadingskip = 9pt plus 4pt minus 2pt",
  "",
  "@ifclear smallbook",
  "\\global\\parskip 6pt plus 1pt",
  "@end ifclear",
  "@end tex",
  "@setchapternewpage odd",
  "@ifinfo",
  "@top ",
  "@node Top, (dir), (dir), (dir)",
  "@end ifinfo",
  "@c ",
  "@c This is the end of the header block",
  "@c "
};

void FormatToTexinfo( void )
{
  Line_Control *line;
  Line_Control *new_line;
  char          Buffer[ PARAGRAPH_SIZE ];
  int           i;
  char          ChapterTitle[ PARAGRAPH_SIZE ];
  char          InfoFile[ PARAGRAPH_SIZE ];
  char          *p;
  boolean        new_section; 
  boolean        in_bullets; 

  for ( line = (Line_Control *) Lines.first ;
        !_Chain_Is_last( &line->Node ) ; 
      ) {

    switch ( line->keyword ) {
      case UNUSED:
        line = (Line_Control *) line->Node.next;
        break;

      case OBJECT:
        LineCopyFromRight( line, ChapterTitle );
        strcpy( InfoFile, ChapterTitle );

        for ( p=InfoFile ; *p ; *p++ )   /* turn this into a file name */
          if ( isspace( *p ) )
            *p = '_';             

        sprintf( Buffer, "@chapter %s", line->Contents );
        strcpy( line->Contents, Buffer );
        line = (Line_Control *) line->Node.next;
        break;

      case ATTRIBUTE_DESCRIPTIONS:
      case ASSOCIATION_DESCRIPTIONS:
      case ABSTRACT_TYPE_DESCRIPTIONS:
      case DATA_ITEM_DESCRIPTIONS:
      case METHOD_DESCRIPTIONS:
      case TASK_DESCRIPTIONS:
        sprintf( Buffer, "@section %s", line->Contents );
        strcpy( line->Contents, Buffer );

        new_line = AllocateLine();
        strcpy( new_line->Contents, "@page" );
        _Chain_Insert( line->Node.previous, &new_line->Node );

        line = (Line_Control *) line->Node.next;
        new_section = TRUE;
        break;

      case END_OBJECT:
        line->Contents[ 0 ] = '\0';
        goto bottom;

      case ATTRIBUTE:
      case ASSOCIATION:
      case ABSTRACT_TYPE:
      case DATA_ITEM:
        if ( !new_section ) {
          new_line = AllocateLine();
          strcpy( new_line->Contents, "@need 4000" );
          _Chain_Insert( line->Node.previous, &new_line->Node );
        }
        new_section = FALSE;
        sprintf( Buffer, "@subsection %s", line->Contents );
        strcpy( line->Contents, Buffer );
        line = (Line_Control *) line->Node.next;
         break;

      case METHOD:
      case TASK:
        if ( !new_section ) {
          new_line = AllocateLine();
          strcpy( new_line->Contents, "@page" );
          _Chain_Insert( line->Node.previous, &new_line->Node );
        }
        new_section = FALSE;
        sprintf( Buffer, "@subsection %s", line->Contents );
        strcpy( line->Contents, Buffer );
        line = (Line_Control *) line->Node.next;
        break;

      case DESCRIPTION:
      case COPYRIGHT:
      case PORTING:
      case THEORY_OF_OPERATION:
      case DEPENDENCIES:
      case NOTES:
        sprintf( Buffer, "@subheading %s\n", line->Contents );
        strcpy( line->Contents, Buffer );
        line = (Line_Control *) line->Node.next;

        /* now take care of the paragraphs which are here */

        in_bullets = FALSE;
        do {
          line = (Line_Control *) line->Node.next;
          if ( line->format == BULLET_OUTPUT ) {
            if ( !in_bullets ) {
              in_bullets = TRUE;

              new_line = AllocateLine();
              _Chain_Insert( line->Node.previous, &new_line->Node );

              new_line = AllocateLine();
              strcpy( new_line->Contents, "@itemize @bullet" );
              _Chain_Insert( line->Node.previous, &new_line->Node );
            }
            sprintf( Buffer, "@item %s\n", line->Contents );
            strcpy( line->Contents, Buffer );
          } else if ( in_bullets ) {
            in_bullets = FALSE;

            new_line = AllocateLine();
            strcpy( new_line->Contents, "@end itemize" );
            _Chain_Insert( line->Node.previous, &new_line->Node );

            new_line = AllocateLine();
            _Chain_Insert( line->Node.previous, &new_line->Node );
          } else {
            new_line = AllocateLine();
            _Chain_Insert( line->Node.previous, &new_line->Node );
          }
        } while ( !line->keyword );

        break;

      case DERIVATION:
      case TYPE:
      case RANGE:
      case UNITS:
      case SCALE_FACTOR:
      case TOLERANCE:
      case VISIBILITY:
      case ASSOCIATED_WITH:
      case MULTIPLICITY:
      case TIMING:
        sprintf( Buffer, "@subheading %s\n", line->Contents );
        strcpy( line->Contents, Buffer );
        line = (Line_Control *) line->Node.next;
        break;

      case MEMBERS:
      case DEFAULT:
      case REQUIREMENTS:
      case REFERENCES:
      case INPUTS:
      case OUTPUTS:
      case PDL:
      case SYNCHRONIZATION:
        sprintf( Buffer, "@subheading %s\n", line->Contents );
        strcpy( line->Contents, Buffer );

        /* now take care of the raw text which is here */

        new_line = AllocateLine();
        strcpy( new_line->Contents, "@example" );
        _Chain_Insert( &line->Node, &new_line->Node );

        do {
          line = (Line_Control *) line->Node.next;
          if ( !strlen( line->Contents ) ) {
            new_line = AllocateLine();
            strcpy( new_line->Contents, "@end example" );
            _Chain_Insert( line->Node.previous, &new_line->Node );

            new_line = AllocateLine();
            strcpy( new_line->Contents, "@example" );
            _Chain_Insert( &line->Node, &new_line->Node );

          } 
        } while ( !line->keyword );

        new_line = AllocateLine();
        strcpy( new_line->Contents, "@end example" );
        _Chain_Insert( line->Node.previous, &new_line->Node );
        /* at this point line points to the next keyword */
        break;

      case RAW_EXAMPLE:

        /* now take care of the raw text which is here */

        new_line = AllocateLine();
        strcpy( new_line->Contents, "@example" );
        _Chain_Insert( line->Node.previous, &new_line->Node );

        while ( line->keyword == RAW_EXAMPLE ) {
          line = (Line_Control *) line->Node.next;
        }

        new_line = AllocateLine();
        strcpy( new_line->Contents, "@end example" );
        _Chain_Insert( line->Node.previous, &new_line->Node );

        /* at this point line points to the next keyword */
        break;

    }   
  }

bottom:
#if 0
  for ( i=NUMBER_ELEMENTS( Texinfo_Headers ) - 1 ; i >= 0 ; i-- ) {
    new_line = AllocateLine();
    strcpy( new_line->Contents, Texinfo_Headers[ i ] );
    if ( !strcmp( "@setfilename ", new_line->Contents ) )
      strcat( new_line->Contents, ChapterTitle );
    else if ( !strcmp( "@settitle ", new_line->Contents ) )
      strcat( new_line->Contents, InfoFile );
    else if ( !strcmp( "@top ", new_line->Contents ) )
      strcat( new_line->Contents, InfoFile );
    _Chain_Insert( _Chain_Head( &Lines ), &new_line->Node );
  }

  /*
   *  Remove the special end of object string.  No one wants to see 
   *  it in the printed output and the node is already marked "END_OBJECT".
   */

  ((Line_Control *)Lines.last)->Contents[ 0 ] = '\0';

  new_line = AllocateLine();
  strcpy( new_line->Contents, "@bye" );
  _Chain_Append( &Lines, &new_line->Node );

#endif
  if ( Verbose )
    fprintf( stderr, "-------->INSERTING TEXINFO MENUS\n" );

  BuildTexinfoNodes();
}

/*
 *  FormatToWord
 */

void FormatToWord( void )
{
  Line_Control *line;
  Line_Control *new_line;
  char          Buffer[ PARAGRAPH_SIZE ];
  int           i;
  int           length;
  char          ChapterTitle[ PARAGRAPH_SIZE ];
  char          InfoFile[ PARAGRAPH_SIZE ];
  char         *p;
  boolean       new_section; 
  boolean       in_bullets; 

  for ( line = (Line_Control *) Lines.first ;
        !_Chain_Is_last( &line->Node ) ; 
      ) {

    switch ( line->keyword ) {
      case UNUSED:
        line = (Line_Control *) line->Node.next;
        break;

      case OBJECT:
        LineCopyFromRight( line, ChapterTitle );
        strcpy( InfoFile, ChapterTitle );

        for ( p=InfoFile ; *p ; *p++ )   /* turn this into a file name */
          if ( isspace( *p ) )
            *p = '_';             

        sprintf( Buffer, "@Chapter = %s", line->Contents );
        strcpy( line->Contents, Buffer );
        line = (Line_Control *) line->Node.next;
        break;

      case ATTRIBUTE_DESCRIPTIONS:
      case ASSOCIATION_DESCRIPTIONS:
      case ABSTRACT_TYPE_DESCRIPTIONS:
      case DATA_ITEM_DESCRIPTIONS:
      case METHOD_DESCRIPTIONS:
      case TASK_DESCRIPTIONS:
        sprintf( Buffer, "@Section = %s", line->Contents );
        strcpy( line->Contents, Buffer );

        new_line = AllocateLine();
        strcpy( new_line->Contents, "@Page" );
        _Chain_Insert( line->Node.previous, &new_line->Node );

        line = (Line_Control *) line->Node.next;
        new_section = TRUE;
        break;

      case END_OBJECT:
        line->Contents[ 0 ] = '\0';
        goto bottom;

      case ATTRIBUTE:
      case ASSOCIATION:
      case ABSTRACT_TYPE:
      case DATA_ITEM:
        if ( !new_section ) {
          /*
           *  Do something with the style to keep subsection
           *  contents together
           */
          ;
        }
        new_section = FALSE;
        sprintf( Buffer, "@Subsection = %s", line->Contents );
        strcpy( line->Contents, Buffer );
        line = (Line_Control *) line->Node.next;
        break;

      case METHOD:
      case TASK:
        if ( !new_section ) {
          new_line = AllocateLine();
          strcpy( new_line->Contents, "@Page" );
          _Chain_Insert( line->Node.previous, &new_line->Node );
        }
        new_section = FALSE;
        sprintf( Buffer, "@Subsection = %s", line->Contents );
        strcpy( line->Contents, Buffer );
        line = (Line_Control *) line->Node.next;
        break;

      case DESCRIPTION:
      case COPYRIGHT:
      case PORTING:
      case THEORY_OF_OPERATION:
      case DEPENDENCIES:
      case NOTES:
        sprintf( Buffer, "@Subheading = %s\n", line->Contents );
        strcpy( line->Contents, Buffer );
        line = (Line_Control *) line->Node.next;

        /* now take care of the paragraphs which are here */

        in_bullets = FALSE;
        do {
          line = (Line_Control *) line->Node.next;
          if ( line->format == BULLET_OUTPUT ) {
            if ( !in_bullets ) {
              in_bullets = TRUE;

              new_line = AllocateLine();
              _Chain_Insert( line->Node.previous, &new_line->Node );
            }
            sprintf( Buffer, "@Bullet = %s\n", line->Contents );
            strcpy( line->Contents, Buffer );
          } else if ( in_bullets ) {
            in_bullets = FALSE;

            new_line = AllocateLine();
            _Chain_Insert( line->Node.previous, &new_line->Node );
          } else {
            new_line = AllocateLine();
            _Chain_Insert( line->Node.previous, &new_line->Node );
          }
        } while ( !line->keyword );

        break;

      case DERIVATION:
      case TYPE:
      case RANGE:
      case UNITS:
      case SCALE_FACTOR:
      case TOLERANCE:
      case VISIBILITY:
      case ASSOCIATED_WITH:
      case MULTIPLICITY:
      case TIMING:
        sprintf( Buffer, "@Subheading = %s\n", line->Contents );
        strcpy( line->Contents, Buffer );
        line = (Line_Control *) line->Node.next;
        break;

      case MEMBERS:
      case DEFAULT:
      case REQUIREMENTS:
      case REFERENCES:
      case INPUTS:
      case OUTPUTS:
      case PDL:
      case SYNCHRONIZATION:
        sprintf( Buffer, "@Subheading = %s\n", line->Contents );
        strcpy( line->Contents, Buffer );
        line = (Line_Control *) line->Node.next;

        /* now take care of the raw text which is here */

#if 0
        while ( !line->keyword ) {
          sprintf( Buffer, "@Example = %s\n", line->Contents );
          strcpy( line->Contents, Buffer );
          line = (Line_Control *) line->Node.next;
        }

        /* at this point line points to the next keyword */
#endif

        /* now take care of the raw text which is here */

        new_line = AllocateLine();
        _Chain_Insert( line->Node.previous, &new_line->Node );
        strcpy( new_line->Contents, "@Example = " );

        while ( !line->keyword ) {
          if ( strlen( line->Contents ) ) {
            new_line->keyword = line->keyword;
            new_line->format = line->format;
            length = strlen(new_line->Contents); 
            if ( (length + strlen(line->Contents) + 12) > PARAGRAPH_SIZE ) {
              fprintf( stderr, "Output line too long at %d\n", line->number );
              exit_application( 1 );
            }

            strcat( new_line->Contents, line->Contents );
            strcat( new_line->Contents, "<@ManualCR>" );
            line = DeleteLine( line );
          } else {
            line = (Line_Control *) line->Node.next;
            new_line = AllocateLine();
            _Chain_Insert( line->Node.previous, &new_line->Node );
            strcpy( new_line->Contents, "@Example = " );
          }
        }

        /* at this point line points to the next keyword */
        break;

      case RAW_EXAMPLE:

        /* now take care of the raw text which is here */

        new_line = AllocateLine();
        _Chain_Insert( line->Node.previous, &new_line->Node );
        strcpy( new_line->Contents, "@Example = " );


        while ( line->keyword == RAW_EXAMPLE ) {
          if ( strlen( line->Contents ) ) {
            new_line->keyword = line->keyword;
            new_line->format = line->format;
            length = strlen(new_line->Contents);
            if ( (length + strlen(line->Contents) + 12) > PARAGRAPH_SIZE ) {
              fprintf( stderr, "Output line too long at %d\n", line->number );
              exit_application( 1 );
            }

            strcat( new_line->Contents, line->Contents );
            strcat( new_line->Contents, "<@ManualCR>" );
            line = DeleteLine( line );
          } else {
            line = (Line_Control *) line->Node.next;
            new_line = AllocateLine();
            _Chain_Insert( line->Node.previous, &new_line->Node );
            strcpy( new_line->Contents, "@Example = " );
          }
        }

        /* at this point line points to the next keyword */
        break;

    }   
  }

bottom:
}

/*
 *  PrintFile
 */

void PrintFile(
  char *out
)
{
  Line_Control *line;

  OutFile = fopen( out, "w+" );

  if ( !OutFile ) {
    fprintf( stderr, "Unable to open (%s) for output\n", out );
    exit_application( 1 );
  }
  assert( OutFile );

  for ( line = (Line_Control *) Lines.first ;
        !_Chain_Is_last( &line->Node ) ; 
        line = (Line_Control *) line->Node.next ) {
    fprintf( OutFile, "%s\n", line->Contents );
/*
    fprintf(
      OutFile,
      "(%d,%d)%s\n",
      line->keyword,
      line->format,
      line->Contents
    );
*/
  }
}

/*
 *  DumpList
 */

void DumpList( 
  Chain_Control  *the_list
)
{
  Line_Control  *line;

  fprintf( stderr, "---> Dumping list (%p)\n", the_list );

  for ( line = (Line_Control *) the_list->first ;
      !_Chain_Is_last( &line->Node ) ; 
      line = (Line_Control *) line->Node.next ) {
    fprintf( stderr, "%s\n", line->Contents );
  }
}

/*
 * ReleaseFile
 */

void ReleaseFile()
{
   Line_Control *line;
   Line_Control *next;

   for ( line = (Line_Control *) Lines.first ;
         !_Chain_Is_last( &line->Node ) ; 
       ) {
     next = (Line_Control *) line->Node.next;
     line = next;
   }
}

/*
 *  strtoInitialCaps
 */

void strtoInitialCaps(
  char *dest,
  char *src
)
{
  char *source = src;
  char *destination = dest;

  if ( !dest )
    return;
  strcpy( dest, src );
#if 0
  source = src;
  destination = (dest) ? dest : src;

  while ( *source ) {
    while ( isspace( *source ) )
      *destination++ = *source++;

    if ( !*source )
      break;

    *destination++ = toupper( *source++ );

    for ( ; *source && !isspace( *source ) ; source++ )
      *destination++ = tolower( *source );
     
    if ( !*source )
      break;
  }

  *destination = '\0';
#endif
}

/*
 *  Validate_visibility
 */
 
char *Valid_visibilities[] = {
  "public",
  "private"
};
 
int Validate_visibility(
  Line_Control   *line
)
{
  char   *s;
  char   *d;
  char    Buffer[ BUFFER_SIZE ];
  char    Visibility[ BUFFER_SIZE ];
  int     i;
  boolean found;
  int     errors = 0;
 
  LineCopyFromRight( line, Buffer );
 
  memset( Visibility, '\0', sizeof( Visibility ) );
  s = Buffer;
 
  for ( d=Visibility ; ; s++, d++ ) {
    *d = *s;
    if ( !*s || isspace(*s) )
      break;
  }
  *d = '\0';
 
  if ( isspace(*s) ) {
    fprintf(
       stderr,
       "Unexpected white space on line %d -- are there multiple words?\n",
       line->number
     );
     errors++;
  }
 
  /*
   *  Check out the type part of this keyword
   */
 
  for ( found=FALSE, i=0 ; i<NUMBER_ELEMENTS(Valid_visibilities) ; i++ ) {
    if ( !strcmp( Valid_visibilities[ i ], Visibility ) ) {
      found = TRUE;
      break;
    }
  }
 
  if ( !found ) {
    if ( !(InsertTBDs && !strcmp( Visibility, "TBD" )) ) {
      fprintf(
        stderr,
        "Invalid visibility type (%s) on line %d\n",
        Visibility,
        line->number
      );
      errors++;
    }
  }
 
  return (errors) ? -1 : 0;
}

/*
 *  Validate_synchronization
 */
 
char *Valid_synchronization[] = {
  "delay",
  "event",
  "mutex",
  "semaphore",
  "message",
  "signal",
  "period"
};
 
boolean Valid_synchronization_text_below[] = {
  FALSE,   /* delay */
  TRUE,    /* event */
  TRUE,    /* mutex */
  TRUE,    /* semaphore */
  TRUE,    /* message */
  TRUE,    /* signal */
  FALSE    /* period */
};

int Validate_synchronization(
  Line_Control   *line
)
{
  char   *s;
  char   *d;
  char    Buffer[ BUFFER_SIZE ];
  char    Synchronization[ BUFFER_SIZE ];
  int     i;
  boolean found;
  int     errors = 0;
 
  LineCopyFromRight( line, Buffer );
 
  memset( Synchronization, '\0', sizeof( Synchronization ) );
  s = Buffer;
 
  for ( d=Synchronization ; ; s++, d++ ) {
    *d = *s;
    if ( !*s || isspace(*s) )
      break;
  }
  *d = '\0';
 
  if ( isspace(*s) ) {
    fprintf(
       stderr,
       "Unexpected white space on line %d -- invalid use of multiple words\n",
       line->number
     );
     errors++;
  }
 
  /*
   *  Check out the type part of this keyword
   */
 
  for ( found=FALSE, i=0 ; i<NUMBER_ELEMENTS(Valid_synchronization) ; i++ ) {
    if ( !strcmp( Valid_synchronization[ i ], Synchronization ) ) {
      found = TRUE;
      break;
    }
  }
 
  if ( !found ) {
    fprintf(
       stderr,
       "Invalid synchronization type (%s) on line %d\n",
       Synchronization,
       line->number
     );
     errors++;
  }

  if ( line->keyword && !Valid_synchronization_text_below[ i ] ) {
    fprintf(
       stderr,
       "Expected text below synchronization type (%s) on line %d\n",
       Synchronization,
       line->number
     );
     errors++;
  }
 
  return (errors) ? -1 : 0;
}

/*
 *  Validate_abstract_type
 *
 * presence of range or members but not both
 */

int Validate_abstract_type(
  Section_info_t *section,
  Line_Control   *start,
  Line_Control   *next_section
)
{
  boolean       range_found = FALSE;
  boolean       members_found = FALSE;
  boolean       enumerated_found = FALSE;
  boolean       no_range_or_members = FALSE;
  boolean       true_found = FALSE;
  boolean       false_found = FALSE;
  Line_Control *line;
  int           errors = 0;

  for ( line = start; 
        line != next_section ; 
        line = (Line_Control *) line->Node.next ) {
    if ( line->keyword == RANGE ) 
      range_found = TRUE;
    else if ( line->keyword == MEMBERS ) {
      members_found = TRUE;
    } else if ( line->keyword == TYPE ) {
      if ( strstr( line->Contents, "enumerated" ) || 
           strstr( line->Contents, "structure" ) )
        enumerated_found = TRUE;
    } else if ( line->keyword == DERIVATION ) {
      if ( strstr( line->Contents, "enumerated" ) || 
           strstr( line->Contents, "structure" ) ) {
        enumerated_found = TRUE;
      } else if ( strstr( line->Contents, "handle" ) || 
                  strstr( line->Contents, "array" ) || 
                  strstr( line->Contents, "string" ) ) {
        no_range_or_members = TRUE;
      }
    }
  }

  if ( no_range_or_members ) {
/* This code does not like:

ABSTRACT TYPE: Times
  DESCRIPTION:
    This type specifies mode elapsed times.
  VISIBILITY: public
  DERIVATION: array of Elapsed_Seconds of Timeutil
  RANGE: COMM - ENGAGE of Current_mode_t

    if ( range_found ) {
       fprintf(
         stderr,
         "Range should not be specified in subsection starting at line %d\n",
         start->number
       );
       errors++;
    }
*/
    if ( members_found ) {
       fprintf(
         stderr,
         "Members should not be specified in subsection starting at line %d\n",
         start->number
       );
       errors++;
    }
  } else {
    if ( !range_found && !members_found ) {
      fprintf(
         stderr,
         "Neither range nor members keyword present in "
           "subsection starting at line %d\n",
         start->number
       );
       errors++;
    }

    if ( !enumerated_found  && !range_found ) {
      fprintf(
         stderr,
         "Type does not have range specified in "
           "subsection starting at line %d\n",
         start->number
       );
       errors++;
    }

  }

  if ( !InsertTBDs ) {
    if ( range_found && members_found ) {
      fprintf(
         stderr,
         "Both range and members keyword present in "
           "subsection starting at line %d\n",
         start->number
       );
       errors++;
    }
  }  

  if ( enumerated_found  && !members_found ) {
    fprintf(
       stderr,
       "Enumerated type without list of members in "
         "subsection starting at line %d\n",
       start->number
     );
     errors++;
  }

  return (errors) ? -1 : 0;
}

/*
 *  Validate_attribute
 *
 * presence of range or members but not both
 */
 
int Validate_attribute(
  Section_info_t *section,
  Line_Control   *start,
  Line_Control   *next_section
)
{
  boolean       range_found = FALSE;
  boolean       members_found = FALSE;
  boolean       enumerated_found = FALSE;
  boolean       boolean_found = FALSE;
  boolean       true_found = FALSE;
  boolean       false_found = FALSE;
  Line_Control *line;
  int           errors = 0;
 
  for ( line = start;
        line != next_section ;
        line = (Line_Control *) line->Node.next ) {
    if ( line->keyword == RANGE )
      range_found = TRUE;
    else if ( line->keyword == MEMBERS ) {
      members_found = TRUE;
      if ( boolean_found == TRUE ) {
        line = (Line_Control *) line->Node.next;
        while ( !_Chain_Is_last( &line->Node ) ) {
          if ( line->keyword )
            break;
          if ( strstr( line->Contents, "FALSE" ) )
            false_found = TRUE;
          else if ( strstr( line->Contents, "TRUE" ) )
            true_found = TRUE;
          line = (Line_Control *) line->Node.next;
        }
        line = (Line_Control *) line->Node.previous;
      }
    } else if ( line->keyword == TYPE || line->keyword == DERIVATION ) {
      if ( strstr( line->Contents, "enumerated" ) )
        enumerated_found = TRUE;
      else if ( strstr( line->Contents, "boolean" ) ) {
        enumerated_found = TRUE;
        boolean_found = TRUE;
      }
    }
  }
 
  if ( !range_found && !members_found ) {
    fprintf(
       stderr,
       "Neither range nor members keyword present in "
         "subsection starting at line %d\n",
       start->number
     );
     errors++;
  }
 
  if ( !InsertTBDs ) {
    if ( range_found && members_found ) {
      fprintf(
         stderr,
         "Both range and members keyword present in "
           "subsection starting at line %d\n",
         start->number
       );
       errors++;
    }
  }
 
  if ( enumerated_found  && !members_found ) {
    fprintf(
       stderr,
       "Enumerated type without list of members in "
         "subsection starting at line %d\n",
       start->number
     );
     errors++;
  }
 
  if ( !enumerated_found  && !range_found ) {
    fprintf(
       stderr,
       "Type does not have range specified in "
         "subsection starting at line %d\n",
       start->number
     );
     errors++;
  }
 
  if ( boolean_found && !true_found ) {
    fprintf(
       stderr,
       "Boolean without a TRUE case specified in "
         "subsection starting at line %d\n",
       start->number
     );
     errors++;
  }
 
  if ( boolean_found && !false_found ) {
    fprintf(
       stderr,
       "Boolean without a FALSE case specified in "
         "subsection starting at line %d\n",
       start->number
     );
     errors++;
  }
 
  return (errors) ? -1 : 0;
}

/*
 *  Validate_object
 *
 * presence of range or members but not both
 */
 
int Validate_object(
  Section_info_t *section,
  Line_Control   *start,
  Line_Control   *next_section
)
{
  char          ObjectName[ BUFFER_SIZE ];
  char          EndObjectName[ BUFFER_SIZE ];
  Line_Control *line;
  int           errors = 0;
 
  
  LineCopyFromRight( start, ObjectName );

  for ( line = start;
        !_Chain_Is_last( &line->Node ) ; 
        line = (Line_Control *) line->Node.next ) {
    if ( line->keyword == END_OBJECT ) {
      LineCopyFromRight( line, EndObjectName );
      break;
    }
  }
 
  if ( strcmp( ObjectName, EndObjectName ) ) {
    fprintf(
       stderr,
       "Object and End Object names do not match\n"
     );
     errors++;
  }

  return (errors) ? -1 : 0;
}

