/* wp2x.c,v 1.9 1994/03/26 19:28:43 mcr Exp */

/* Before compiling, read the section titled `portability concerns'. */

/************************************************************************
 * wp2x.c,v
 * Revision 1.9  1994/03/26  19:28:43  mcr
 * Hacker's guide added.
 * getopt routines included
 * Regression test outputs automated.
 *
 * Revision 1.8  1994/02/13  22:18:35  mcr
 * Fixed bug in -v option
 *
 * Revision 1.7  1994/02/13  22:13:41  mcr
 * Reorganized wp2x files into sub-directories. Updated man page.
 *
 * Revision 1.6  1994/01/06  14:32:28  mcr
 * Checkin of patch1
 *
 * Revision 1.5  1993/09/07  18:19:21  mcr
 * Fixed html.cfg to ignore soft hyphens.
 * Fixed wp2x.c to deal with options correctly.
 *
 * Revision 1.4  1993/09/05  20:29:17  mcr
 * First alpha at 5.1 compatible wp2x.
 *
 * Revision 1.3  1993/09/03  23:09:37  mcr
 * Hacking on files.
 *
 * Revision 1.2  1993/08/27  14:50:17  mcr
 * More hacks to html.cfg.
 *
 * Revision 1.1.1.1  1993/08/23  19:09:21  mcr
 * Local hacks included
 *
 * Revision 1.10  91/08/18  15:05:41  raymond
 * Descriptor file stuff.
 * 
 * Revision 1.9  91/08/06  09:08:09  raymond
 * add missing `break' in check_arity
 * 
 * Revision 1.8  91/08/06  08:31:21  raymond
 * Avoid infinite loop if file is corrupted.
 * Better error-checking on configuration file (new output scheme).
 * 
 * Revision 1.7  91/08/02  13:35:37  raymond
 * Epsilonically better handling of environments that didn't end properly.
 * Change return type of main() to keep gcc quiet.
 * MSC support.
 * 
 * Revision 1.6  91/07/28  21:08:53  raymond
 * BeginTabs et al, FNote#, ENote#, NegateTotal, more unsupported codes
 * Improve character tokens, Header, Footer
 * Take care when people don't end lines with HRt
 * Fix major bugs in endnote processing, footnote numbering (and nobody
 *    noticed!)
 * More worries about signed characters.
 * 
 * Revision 1.5  91/07/23  22:59:43  raymond
 * Add COMMENT token, and some bug fixes.
 * 
 * Revision 1.4  91/07/23  22:09:23  raymond
 * Concessions to slightly non-ANSI compilers. (`const', `unsigned char')
 * More patches for machines with signed characters.
 * Fix blatant bug in hex constants.  (Amazed nobody noticed.)
 * New tags SetFn#, Header, Footer.
 * Warning messages for unsupported tokens.
 * Backslahes processed in character tags.
 * Fixed(?) footnotes, endnotes, page length changes.
 * Inserted missing `break's into the huge switch.
 * 
 * Revision 1.3  91/07/12  15:39:44  raymond
 * Spiffy Turbo C support.
 * Some <stdlib.h>'s don't declare errno et al.
 * Command line switches `-s' and `-n' added.
 * More cute warning messages.
 * Dots periodically emitted.
 * Give the enum of token types a name, to placate QuickC.
 * Fix problems with pitch changes and signed characters.
 * 
 * Revision 1.2  91/06/22  08:18:22  raymond
 * <process.h> and fputchar() aren't sufficiently portable.
 * strerror() fails to exist on some so-called ANSI platforms.
 * Removed assumption that characters are unsigned.
 * Forgot to #include <stdarg.h>
 * 
 */

/************************************************************************
 * PORTABILITY CONCERNS
 ************************************************************************
 *
 * If possible, compile with unsigned characters.  (Though I think
 * I've taken care of all the places where I assumed characters are
 * unsigned.)
 *
 * This program assumes that your compiler is fully ANSI-conformant.
 * Depending on how non-conformant your compiler is, you may need to
 * set the following symbols at compile time:
 *
 * NO_CONST -- set this if your compiler does not know what `const' means.
 * Cdecl    -- how to tag functions that are variadic.
 *
 * Cdecl is used if you need special declarations for variadic functions.
 * This is used by IBM PC compilers so that you can make the default
 * parameter passing Pascal-style or Fastcalls.
 *
 * Some very machine-dependent stuff happens when trying to open the
 * descriptor file.  Please read dopen.c as well.
 */

#ifdef NO_CONST
#define const
#endif

#ifndef Cdecl			/* default is nothing */
#define Cdecl
#endif

/************************************************************************
 * This program divides naturally into two parts.
 *
 * The first part reads in the descriptor file and builds the expansions
 * for each of the identifiers listed above.
 * This is the easy part.
 *
 * The second part reads the input file and uses the expansions collected
 * in the first part to transform the file into the output.
 * This is the hard part.
 *
 ************************************************************************/

/* And now, the code.
 * We start off with some obvious header files.
 */

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/types.h>
#include <unistd.h>
#include <netinet/in.h>
#include <assert.h>

#include "tokens.h"
#include "patchlevel.h"

/* Some platforms do not define these externals in stdlib.h */
extern int Cdecl errno;
extern char *Cdecl sys_errlist[];
extern int Cdecl sys_nerr;

/************************************************************************/
/* Some common idioms                                                   */
/************************************************************************/

#define do_nothing		/* twiddle thumbs */

/************************************************************************/
/* Blowing up                                                           */
/************************************************************************/

/* The function "error" accepts two arguments.  A FILE pointer and
 * a printf-style argument list.  The printf-style arguments are
 * printed to stderr.  If the FILE is non-NULL, the the remaining
 * contents of the file are printed as well (to provide context), up
 * to 80 characters.
 */

void Cdecl
error (FILE * fp, char *fmt,...)
{
  int i;
  va_list ap;

  fputs ("Error: ", stderr);
  va_start (ap, fmt);
  vfprintf (stderr, fmt, ap);
  va_end (ap);
  fputc ('\n', stderr);

  if (fp)
    {
      fprintf (stderr, "Unread text: ");
      for (i = 0; i < 80 && !feof (fp); i++)
	fputc (getc (fp), stderr);
      fputc ('\n', stderr);
    }
  exit (1);
}

/************************************************************************/
/* Command-line switches                                                */
/************************************************************************/
int silent = 0;
int blipinterval = 1024;	/* display blips every 1K */
int blipcount;

/************************************************************************/
/* Basic file manipulations                                             */
/************************************************************************/

/* We here define a few basic functions.  Let us hope that the first
 * three functions' names are self-descriptive.
 */

int
next_non_whitespace (FILE * fp)
{
  register int c;

  while ((c = getc (fp)) != EOF && isspace (c))
    do_nothing;

  return c;
}

int
next_non_space_or_tab (FILE * fp)
{
  register int c;

  while ((c = getc (fp)) != EOF && (c == ' ' || c == '\t'))
    do_nothing;

  return c;
}

void
eat_until_newline (FILE * fp)
{
  register int c;

  while ((c = getc (fp)) != EOF && c != '\n')
    do_nothing;
}

/* The function parse_hex grabs a (no-more-than-two-character) hex
 * constant.  Similarly, parse_octal does the same for octal constants.
 */

int
parse_hex (FILE * fp)
{
  register int c, value;

  if (!isxdigit (c = toupper (getc (fp))))
    error (fp, "Expecting a hex digit");

  if ((value = c - '0') > 9)
    value += '0' - 'A' + 10;

  if (!isxdigit (c = getc (fp)))
    {
      ungetc (c, fp);
      return value;
    }

  c = toupper (c);
  value = (value << 4) + c - '0';
  if (c > '9')
    value += '0' - 'A' + 10;
  return value;
}

int
parse_octal (FILE * fp, register int c)
{
  register int value = c - '0';

  if ((c = getc (fp)) < '0' || c > '7')
    {
      ungetc (c, fp);
      return value;
    }

  value = (value << 3) + c - '0';

  if ((c = getc (fp)) < '0' || c > '7')
    {
      ungetc (c, fp);
      return value;
    }

  return (value << 3) + c - '0';
}


/************************************************************************/
/* Storing the input strings                                            */
/************************************************************************/

/* The input strings are allocated from a large pool we set up at
 * startup.  This lets us do our thing without having to fight
 * with people like malloc and friends.  This method does limit
 * our configuration file to 32K, however.  We hope that this is
 * not a problem.  (It also means that the program can be translated
 * to almost any other language without too much difficulty.)
 *
 * Here's how it works.
 *
 * "pool" is an array of POOL_SIZE characters.  The value of POOL_SIZE
 * is flexible, but shouldn't exceed 65535, since that's the size of
 * an IBM PC segment.  If your configuration file is more than 64K,
 * then there's probably something wrong.
 *
 * "pool_ptr" points to the next character in "pool" that hasn't been
 * used for anything yet.
 *
 * "top_of_pool" points one character beyond the end of pool, so we can
 * see if we've run out of memory.
 *
 * When we want to put something into the pool, we simply store into "pool"
 * and increment "pool_ptr" appropriately.
 *
 * Access to these variables is done through the following functions,
 * implemented as macros.
 *
 * "anchor_string()" is called before you start throwing things into
 * the pool.  It returns a pointer to the beginning of the string
 * being built up.
 *
 * "add_to_string(c)" adds the character "c" to the string being built up.
 *
 * "finish_string()" gets ready for building a new string.  We check
 * that we did not overflow our pool.  We pull the sneaky trick of
 * a dummy else clause so that [1] "else"s match up properly if this
 * is nested inside an "if" statement, [2] the semicolon gets eaten
 * up correctly.
 *
 * "remove_string(s)" removes all strings from the one called "s" onwards.
 *
 */

#define POOL_SIZE   32768U

char pool[POOL_SIZE];
char *pool_ptr = pool;
#define top_of_pool (pool + POOL_SIZE)

#define anchor_string() pool_ptr
#define add_to_string(c) (*pool_ptr++ = c)
#define finish_string() \
     if (pool_ptr >= top_of_pool) error(NULL, "string pool overflow."); \
     else do_nothing
#define remove_string(s) (pool_ptr = s)

char *expansion[LastToken];


/************************************************************************/
/* Naming the identifiers                                               */
/************************************************************************/
/* Extreme care must be taken to ensure that this list parallels the list
 * of token names above.
 */

typedef struct identifier
  {
    char *name;
    int arity;
  }
Identifier;

Identifier names[] =
{
  {"typeout", 0},
  {"BEGIN", 0},
  {"END", 0},
  {"Comment", 0},
  {"comment", 0},
  {"PageNo", 0},
  {"RomanPage", 1},
  {"ArabicPage", 1},
  {"HSpace", 0},
  {"Tab", 0},
  {"BeginTabs", 0},
  {"SetTab", 1},
  {"SetTabCenter", 1},
  {"SetTabRight", 1},
  {"SetTabDecimal", 1},
  {"EndTabs", 0},
  {"HPg", 0},
  {"CondEOP", 1},
  {"HRt", 0},
  {"SRt", 0},
  {"-", 0},			/* NHyph */
  {"--", 0},			/* NHyphE */
  {"=", 0},			/* HHyph */
  {"\\-", 0},			/* DHyph */
  {"\\--", 0},			/* DHyphE */
  {"NoHyphWord", 0},
  {"Marg", 2},
  {"TopMarg", 1},
  {"PageLength", 1},
  {"SS", 0},
  {"DS", 0},
  {"1.5S", 0},			/* OHS */
  {"TS", 0},
  {"LS", 1},
  {"LPI", 1},
  {"Bold", 0},
  {"bold", 0},
  {"Und", 0},
  {"und", 0},
  {"DoubleUnd", 0},
  {"doubleund", 0},
  {"Red", 0},
  {"red", 0},
  {"Strike", 0},
  {"strike", 0},
  {"Rev", 0},
  {"rev", 0},
  {"Outline", 0},
  {"outline", 0},
  {"Fine", 0},
  {"fine", 0},
  {"Over", 0},
  {"over", 0},
  {"Sup", 0},
  {"sup", 0},
  {"Sub", 0},
  {"sub", 0},
  {"Large", 0},
  {"large", 0},
  {"Small", 0},
  {"small", 0},
  {"VeryLarge", 0},
  {"verylarge", 0},
  {"ExtraLarge", 0},
  {"extralarge", 0},
  {"Italics", 0},
  {"italics", 0},
  {"Shadow", 0},
  {"shadow", 0},
  {"SmallCaps", 0},
  {"smallcaps", 0},

  {"UpHalfLine", 0},
  {"DownHalfLine", 0},
  {"AdvanceToHalfLine", 2},
  {"Indent", 0},
  {"DIndent", 0},
  {"indent", 0},
  {"dindent", 0},
  {"MarginRelease", 1},
  {"Center", 0},
  {"center", 0},
  {"CenterHere", 0},
  {"centerhere", 0},
  {"Align", 0},
  {"align", 0},
  {"AlignChar", 1},
  {"FlushRight", 0},
  {"flushright", 0},
  {"Math", 0},
  {"math", 0},
  {"MathCalc", 0},
  {"MathCalcColumn", 0},
  {"SubTotal", 0},
  {"IsSubTotal", 0},
  {"Total", 0},
  {"IsTotal", 0},
  {"GrandTotal", 0},
  {"NegateTotal", 0},
  {"Col", 0},
  {"col", 0},
  {"Fn", 0},
  {"fn", 0},
  {"En", 0},
  {"en", 0},
  {"SetFn#", 1},
  {"FNote#", 0},
  {"ENote#", 0},
  {"Figure#", 0},
  {"TableMarker", 0},
  {"Hyph", 0},
  {"hyph", 0},
  {"Just", 0},
  {"just", 0},
  {"Wid", 0},
  {"wid", 0},
  {"HZone", 2},
  {"DAlign", 1},
  {"Header", 0},
  {"header", 0},
  {"Footer", 0},
  {"footer", 0},
  {"Supp", 1},
  {"CtrPg", 0},
  {"SetFont", 2},
  {"SetBin", 1},
  {"PN0", 0},
  {"PN1", 0},
  {"PN2", 0},
  {"PN3", 0},
  {"PN4", 0},
  {"PN5", 0},
  {"PN6", 0},
  {"PN7", 0},
  {"PN8", 0},
  {NULL, 0},			/* UnsupportedPlaceHolder -- keeps match_identifier happy */
  {"set page number column", 0},
  {"extended tabs", 0},
  {"underline mode", 0},
  {"define column", 0},
  {"footnote attributes", 0},
  {"paragraph numbering style", 0},
  {"numbered paragraph", 0},
  {"begin marked text", 0},
  {"end marked text", 0},
  {"define marked text", 0},
  {"define index mark", 0},
  {"define math columns", 0},
  {"WPCorp obsolete", 0},
  {"WPCorp reserved", 0},
  {"Merge Code", 0},
  {"WPCorp undefined", 0},
};

/* The file pointer "descriptor" points to our descriptor file
 * and "input" points to our input file.
 *
 * Kinda makes sense that way.
 */

FILE *descriptor, *input;

/* And the function match_identifier(s) takes a string and converts
 * it to its corresponding integer.  Or blows up if it couldn't
 * find one.
 */

int
match_identifier (const char *s)
{
  Identifier *I;

  /* Maybe it is a special character */
  if (s[0] == '\'' && s[2] == '\'' && s[3] == '\0')
    return (int) (unsigned char) s[1];

  /* Else it must be a multi-character guy */
  for (I = names; I->name; I++)
    if (!strcmp (I->name, s))
      return typeout + (I - names);

  /* Otherwise, I don't know what to do with it */
  error (descriptor, "Unknown identifier %s", s);
  /*NOTREACHED */
  return 0;
}

/* check_arity ensures that the expansion string is valid */
void
check_arity (int ident, char *t)
{
  char *s;
  int arity = 0;
  if (ident > typeout)
    arity = names[ident - typeout].arity;
  for (s = t; *s; s++)
    {
      if (*s != '%')
	continue;
      switch (*++s)
	{
	case '\n':
	  if (s != t + 1)
	    error (descriptor, "%s: `%%\\n' not at start of expansion",
		   names[ident - typeout].name);
	  break;
	case '1':
	case 'c':
	  if (arity < 1)
	    goto bad_escape;
	  break;
	case '2':
	  if (arity < 2)
	    goto bad_escape;
	  break;
	case '%':
	  break;
	default:
	bad_escape:
	  error (descriptor, "%s: invalid escape `%%%c'", names[ident - typeout].name, *s);
	}
    }
}

/* expand_backslash() is called when a backslash is encountered in
 * the descriptor file.  Its job is to parse a backslash-sequence.
 * The usual C-escapes (\a \b \f \n \r \t \v) are understood, as
 * well as the octal escape \000 [up to three octal digits] and
 * the hex escape \xFF [up to two hex digits].
 */

int
expand_backslash (FILE * d)
{
  int c;

  switch (c = getc (d))
    {
    case 'a':
      c = '\a';
      break;
    case 'b':
      c = '\b';
      break;
    case 'f':
      c = '\f';
      break;
    case 'n':
      c = '\n';
      break;
    case 'r':
      c = '\r';
      break;
    case 't':
      c = '\t';
      break;
    case 'v':
      c = '\v';
      break;
    case 'x':
    case 'X':
      c = parse_hex (d);
      break;
    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
      c = parse_octal (d, c);
      break;
    default:			/* c = c; */
      break;
    }
  return c;
}

/* The function read_identifier() attempts to match an identifier
 * in the descriptor file.  It returns EOF if the end of the descriptor
 * file was reached, or the code of the identifier we found.
 * (or blows up if an error was detected.)
 * We build the identifier in "s", with the help of our
 * pool-managing functions above, then discard it, immediately,
 * since we don't use it any more.
 */

int
read_identifier (FILE * d)
{
  register int c;		/* A character we have read */
  char *s;			/* The identifier we are building */
  int ident;			/* The identifier we found */

  /* Skip over comments */
  while ((c = next_non_whitespace (d)) == '#')
    eat_until_newline (d);

  if (c == EOF)
    return EOF;

  /* At this point, "c" contains the first letter of a potential
   * identifier.  Let's see what it could possibly be.
   */
  s = anchor_string ();
  if (c == '\'')
    {				/* a character token */
      add_to_string (c);
      if ((c = getc (d)) == '\\')
	c = expand_backslash (d);
      add_to_string (c);
      if ((c = getc (d)) != '\'')
	error (descriptor, "Invalid character identifier");
      add_to_string (c);
      c = next_non_space_or_tab (d);
    }
  else
    do
      {				/* a name token */
	add_to_string (c);
	c = next_non_space_or_tab (d);
	if (c == '\\')
	  c = expand_backslash (d);
      }
    while (c != EOF && c != '=' && c != '\n');

  if (c != '=')
    error (d, "Identifier not followed by = sign");
  /* A boo-boo.  Something bad happened. */

  add_to_string ('\0');		/* Make it a standard C string. */
  finish_string ();

  ident = match_identifier (s);	/* Go find one. */

  remove_string (s);		/* And we're done with it now. */

  return ident;
}

/* The function grab_expansion() reads expansion text from the
 * descriptor file and adds it to the pool, returning a pointer
 * to the string it just created.
 *
 * After anchoring a new string, we look for the opening quotation
 * mark, then start gobbling characters.  Everything gets copied
 * straight into the string.
 *
 */

char *
grab_expansion (FILE * d)
{
  register int c;		/* Characters being read */
  char *s;			/* The string we are building */

  s = anchor_string ();

  if (next_non_whitespace (d) != '\"')
    error (d, "Quotation mark expected");

  /* Now read the stream until we hit another quotation mark. */

  while ((c = getc (d)) != EOF && c != '\"')
    {
      if (c == '\\')
	c = expand_backslash (d);
      add_to_string (c);
    }
  add_to_string ('\0');
  finish_string ();
  return s;
}

/* Ah, now with all of these beautiful functions waiting for us,
 * we can now write our first Useful Function:  do_descriptor_file.
 * It reads the descriptor file and loads up the "expansion" array
 * with the text expansions we are reading from the file.
 *
 * If we grabbed the expansion of a "typeout", we type it out
 * and discard the string.
 *
 * We stop when the descriptor file runs dry.
 *
 */

void
do_descriptor_file (FILE * d)
{
  register int ident;

  while ((ident = read_identifier (d)) != EOF)
    {
      expansion[ident] = grab_expansion (d);
      if (ident == typeout && !silent)
	{
	  fputs (expansion[typeout], stderr);
	  remove_string (expansion[typeout]);
	  expansion[typeout] = NULL;
	}
      else
	check_arity (ident, expansion[ident]);
    }
}

/************************************************************************/
/* Reading from the input file                                          */
/************************************************************************/

/* The function verify(c) checks that the next character in the input
 * stream is indeed "c".  It eats the character, if all is well.
 * If something went wrong, we complain to stderr, but keep going.
 */

void
verify (state * st, int c)
{
  int d = getc (st->infile);
  if (d != c)
    fprintf (stderr, "Warning: Expected %02X but received %02X at pos: %ld.\n", c, d, ftell (st->infile));
}

/* The function gobble(n) simply eats "n" characters from the input
 * file.
 */
void
gobble (state * st, int n)
{
  while (n--)
    (void) getc (st->infile);
}

int last_HRt = 0;		/* most recent output was HRt */

/* Processing a special code simply entails dumping its expansion.
 * If the expansion is NULL, then we either
 *   [1] print nothing, if it is a code,
 *   [2] print the character itself, if it is an ASCII character.
 *
 * In dumping its expansion, we expand the following percent-escapes:
 *
 *  The percent-escapes are:
 *      %\n  -- newline if previous character was not a newline
 *              (meaningful only as first character in sequence)
 *      %1   -- first parameter, in decimal form
 *      %2   -- second parameter, in decimal form
 *      %c   -- first parameter, in character form
 *      %%   -- literal percent sign
 *
 *  all other %-escapes are flagged as warnings (but should never occur,
 *  since they are trapped at the time the descriptor file is read.)
 */

void
process (state * st, int c, int d1, int d2)
{
  char *s;
  static int last_newline = 0;

  last_HRt = 0;			/* the killer switch sets this */

  if (expansion[c] == NULL)
    {				/* invent a default action */
      if (c < 256)
	{			/* single character */
	  expansion[c] = anchor_string ();	/* emits itself */
	  add_to_string (c);
	  add_to_string ('\0');
	  finish_string ();
	  if (!silent)
	    fprintf (stderr, "Warning: No expansion for %02X (%c)\n", c, c);
	}
      else
	{			/* provide null expansion */
	  expansion[c] = "";
	  if (!silent)
	    {
	      fprintf (stderr, "Warning: No expansion for %s\n", names[c - typeout].name);
	    }
	}
    }

  s = expansion[c];
  if (!*s)
    return;			/* the rest of the code assumes non-null string */
  do
    {
      if (*s != '%')
	putc (*s++, st->outfile);
      else
	{
	  s++;
	  switch (*s++)
	    {
	    case '\n':
	      if (!last_newline)
		putc ('\n', st->outfile);
	      break;
	    case '1':
	      fprintf (st->outfile, "%d", d1);
	      break;
	    case '2':
	      fprintf (st->outfile, "%d", d2);
	      break;
	    case 'c':
	      putc (d1, st->outfile);
	      break;
	    case '%':
	      putc ('%', st->outfile);
	      break;
	    default:
	      fprintf (stderr, "Internal error:  Invalid escape, %%%c\n", s[-1]);
	      break;
	    }
	}
    }
  while (*s);
  last_newline = s[-1] == '\n';
}

void
unsupported (state * st, int b, int c)
{
  if (!silent && !expansion[c])
    {
      expansion[c] = "";
      fprintf (stderr, "Warning: `%s' [%02X] code not supported\n", names[c - typeout].name, b);
    }
  process0 (st, Comment);
  fputs (names[c - typeout].name, stdout);
  process0 (st, eComment);
}

/* The function gobble_until(c) eats characters from the input file
 * until it reaches a c or reaches EOF.
 */
void
gobble_until (state * st, int c)
{
  int i;
  while ((i = getc (st->infile)) != EOF && (int) (unsigned char) i != c)
    do_nothing;
}

/* line_spacing(l) is called whenever we hit a line-spacing-change command.
 * The argument is the desired line spacing, multiplied by two.
 * So single spacing gets a 2, 1.5 spacing gets a 3, etc.
 */
void
line_spacing (state * st, int l)
{
  switch (l)
    {
    case 2:
      process0 (st, SS);
      break;
    case 3:
      process0 (st, OHS);
      break;
    case 4:
      process0 (st, DS);
      break;
    case 6:
      process0 (st, TS);
      break;
    default:
      process1 (st, LS, l);
      break;
    }
}

int environment_status = 0;	/* cleanup at HRt */

void
leave_environment (state * st, int force_HRt)
{
  if (environment_status)
    {
      process0 (st, environment_status);
      environment_status = 0;
    }
  if (force_HRt && !last_HRt)
    process0 (st, HRt);
}

/* The "note_status" flag has one of three values:
 *    0   if we are not inside a note
 *    1   if we are inside a footnote
 *    2   if we are inside an endnote
 *
 * The function handle_note() is called to deal with footnotes and
 * endnotes.  It adjusts the note_status accordingly.
 */

int note_status = 0;

void
handle_note (state * st)
{
  if (note_status)
    {
      leave_environment (st, 1);
      process0 (st, note_status);
      note_status = 0;
    }
  else
    {				/* Decide whether it is an endnote or a footnote */
      if (getc (st->infile) & 2)
	{
	  process0 (st, En);
	  note_status = eEn;
	  gobble (st, 5);
	}
      else
	{
	  process0 (st, Fn);
	  note_status = eFn;
	  gobble (st, 7);
	}
      verify (st, 0xFF);
      gobble (st, 2);		/* margins */
    }
}

/* The tab_table is a bit field.  Each set bit represents a tabstop.
 * Note, however, that the bits are counted from MSB to LSB.
 *
 * The tab_attribute_table is a nybble field.  The n'th nybble represents
 * the attributes of the n'th tabstop.
 */
unsigned char tab_table[32];
unsigned char tab_attribute_table[20];
int next_attribute;

void
process_tab_attribute (state * st, int i)
{
  int b;

  if (next_attribute & 1)
    b = tab_attribute_table[next_attribute / 2] & 3;
  else
    b = (tab_attribute_table[next_attribute / 2] / 16) & 3;
  next_attribute++;

  /* Bottom two bites define what kind of tab.
   * Bit 2 is set if we need dot filling.
   * Bit 3 is unused.
   * We `&3' above because we won't support dot filling.
   */
  process1 (st, SetTab + b, i);
}

void
process_tab_table (state * st)
{
  int i;
  next_attribute = 0;

  process0 (st, BeginTabs);
  for (i = 0; i < 32; i++)
    {
      if (tab_table[i] == 0)
	continue;		/* early out */
      if (tab_table[i] & 0x80)
	process_tab_attribute (st, i * 8 + 0);
      if (tab_table[i] & 0x40)
	process_tab_attribute (st, i * 8 + 1);
      if (tab_table[i] & 0x20)
	process_tab_attribute (st, i * 8 + 2);
      if (tab_table[i] & 0x10)
	process_tab_attribute (st, i * 8 + 3);
      if (tab_table[i] & 0x08)
	process_tab_attribute (st, i * 8 + 4);
      if (tab_table[i] & 0x04)
	process_tab_attribute (st, i * 8 + 5);
      if (tab_table[i] & 0x02)
	process_tab_attribute (st, i * 8 + 6);
      if (tab_table[i] & 0x01)
	process_tab_attribute (st, i * 8 + 7);
    }
  process0 (st, EndTabs);
}

void
handle_tabs (state * st)
{
  /* pad the tables to force no new tabs, and left tabs everywhere */
  memset (tab_table, 0, sizeof (tab_table));
  memset (tab_attribute_table, 0, sizeof (tab_attribute_table));

  fread (tab_table, 20, 1, input);	/* old-style tabs */
  process_tab_table (st);
}

void
handle_extended_tabs (state * st)
{
  fread (tab_table, 32, 1, input);
  fread (tab_attribute_table, 20, 1, input);
  process_tab_table (st);
}

/* The FF_status flag tells us what we should do when we encounter an 0xFF.
 * It contains the token code of the active code, or 0 if no code is active.
 */

int FF_status = 0;

void
handle_FF (state * st)
{
  if (FF_status)
    {				/* finish header/footer */
      leave_environment (st, 1);
      process0 (st, FF_status);
      gobble (st, 2);
      verify (st, 0xD1);
      FF_status = 0;
    }
  else
    process0 (st, 0xFF);
}

/*
 * read short
 */

short
read_word (FILE * f)
{
  short a;
  int c;

  c = getc (f);
  a = c;
  c = getc (f);
  a = (a | c << 8);

  return (a);
}

long
read_dword (FILE * f)
{
  long a;
  int c1, c2, c3, c4;

  c1 = getc (f);
  c2 = getc (f);
  c3 = getc (f);
  c4 = getc (f);

  a = (c4 << 24) | (c3 << 16) | (c2 << 8) | (c1);
  return (a);
}

/*
 * Safe memory allocator 
 */

void *
safemalloc (int size)
{
  void *memory;

  memory = malloc (size);

  if (!memory)
    {
      fprintf (stderr, "Ran out of memory trying to allocate %d bytes\n", size);
      abort ();
    }

  return (memory);
}



/* The function process_token does all of the real work.
 * Given the first character of a token, we eat up everything
 * that belongs to that token.  This routine might be called
 * recursively, since some tokens are defined in terms of other
 * tokens.  (For example, the subscript code is expanded as
 *   [Sub] <character being subscripted> [sub]
 * and the <character being subscripted> might involve other token
 * expansions; specifically, it might be an IBM Extended character.)
 *
 * Luckily, most of our tokens are not recursive.  The macro
 *     bracket(before, after)
 * does the recursive stuff for us, bracketing the next token
 * between expansions of "before" and "after".
 *
 */

#define bracket(before,after) process0(st,before); process_token(); \
                              process0(st,after);

int
process_token (state * st)
{
  int c = getc (st->infile);
  int len;
  struct WPToken *wpt;
  char smallBuf[256];
  struct VariableCode *group;
  int stat;

  if (c == EOF)
    return 0;

  if (!--blipcount && !silent)
    {
      blipcount = blipinterval;
      putc ('.', stderr);
    }

  wpt = &wp_tokens[c];

  st->lastToken = wpt;

  stat = 1;

  switch (wpt->wp_type)
    {
    case reserved:
      /* ignore it! */
      if (!silent)
	{
	  fprintf (stderr, "Reserved code [%02x] seen\n", c);
	}
      break;

    case literal:
      /* output it */
      putc (wpt->wp_intdata, st->outfile);
      break;

    case singleByte:
      assert (wpt->wp_handleFunction != NULL);
      (*wpt->wp_handleFunction) (st, wpt, NULL);
      break;

    case fixedLength:
      smallBuf[0] = c;
      if (fread (smallBuf + 1, wpt->wp_size - 1, 1, st->infile) != 1)
	{
	  perror ("Reading fixedLength");
	  exit (5);
	}
      assert (wpt->wp_handleFunction != NULL);
      if (smallBuf[wpt->wp_size - 1] != wpt->wp_code)
	{
	  fprintf (stderr, "Fixed Length block [%02x] incorrectly terminated by [%02x] at pos %ld\n",
	      wpt->wp_code, smallBuf[wpt->wp_size - 1], ftell (st->infile));
	}
      else
	{
	  stat = (*wpt->wp_handleFunction) (st, wpt, smallBuf);
	}
      break;

    case varLength:
      /* get subfunction */
      c = getc (input);
      len = read_word (input);
      group = (struct VariableCode *) safemalloc (sizeof (struct VariableCode) + len);

      if (fread (group->data, len, 1, input) != 1)
	{
	  perror ("reading variable group");
	  exit (5);
	}
      group->func = wpt->wp_code;
      group->subFunc = c;
      group->len = len;
      assert (wpt->wp_handleFunction != NULL);
      stat = (*wpt->wp_handleFunction) (st, wpt, (void *) group);
      free (group);
      break;
    }
  return stat;
}

/* Now do the other Useful Function.
 */
int
process_input (state * st)
{
  process0 (st, BEGIN);
  while (process_token (st))
    do_nothing;
  process0 (st, END);
  return 1;
}

int
wptoh (int wpl)
{
  char *p, *q;
  int result;

  /* convert to network byte order */
  /* WP is Intel byte order        */
  p = (char *) (&wpl);
  q = (char *) (&result);

  q[0] = p[3];
  q[1] = p[2];
  q[2] = p[1];
  q[3] = p[0];
  return (htonl (result));
}

static char *WPproducts[] =
{
  "none",
  "WordPerfect",
  "Shell",
  "Notebook",
  "Calculator",
  "File Manager",
  "Calendar",
  "Program Editor/Ed Editor",
  "Macro Editor",
  "Plan Perfect",
  "DataPerfect",
  "Mail",
  "Printer",
  "Scheduler",
  "WordPerfect Office",
  "DrawPerfect",
  "LetterPerfect",
};
#define MAX_WP_PRODUCT 16


int
process_preamble (state * st)
{
  int c;
  char prefix[16];
  int documentAreaOffset;

  c = getc (st->infile);
  if (c != 255)
    {
      /* not a WP 5 file! */
      return 0;
    }

  prefix[0] = c;

  if (fread (prefix + 1, 15, 1, st->infile) != 1)
    {
      perror ("No prefix block");
      return 0;
    }

  /* check for WP Corp token */
  if (prefix[1] != 'W' || prefix[2] != 'P' || prefix[3] != 'C')
    {
      fprintf (stderr, "Not a recognized file type: %02x %02x %02x\n",
	       prefix[1], prefix[2], prefix[3]);
      return 0;
    }

  documentAreaOffset = *((int *) &prefix[4]);
  documentAreaOffset = wptoh (documentAreaOffset);
  fseek (st->infile, documentAreaOffset, SEEK_SET);

  fprintf (stderr, "Document created with %s %d.%d\nDocument starts at: %d [%4x]\n",
	   (prefix[8] > MAX_WP_PRODUCT) ? "unknown" : WPproducts[prefix[8]],
	   (prefix[10] == 0 ? 5 : prefix[10]),
	   prefix[11], documentAreaOffset, documentAreaOffset);
  return 1;
}


/************************************************************************/
/* The main program                                                     */
/************************************************************************/

/* First, a pretty little function which tries to open a file and
 * complains loudly if it cannot.
 */

FILE *
efopen (const char *s, const char *m)
{
  FILE *fp = fopen (s, m);

  if (fp == NULL)
    {
      fprintf (stderr, "Error: Cannot open %s", s);
      if (errno > 0 && errno < sys_nerr)
	fprintf (stderr, " (%s)\n", s, sys_errlist[errno]);
      fprintf (stderr, "\n");
      exit (1);
    }

  return fp;
}

extern FILE *dopen (char *);

/* Our main program does very little, really.

 * After checking the command line, it proceeds to open the descriptor
 * file in text mode, and the input file in binary mode.
 * It then calls our two Useful Functions in turn, closing each file
 * after it has served its purpose.
 */
int
main (int argc, char **argv)
{
  extern char *optarg;
  extern int optind;
  int errflg = 0;
  int c;
  char *descriptorFileName;
  state masterState;

  while ((c = getopt (argc, argv, "sn:v")) != -1)
    {
      switch (c)
	{
	case 's':
	  silent = 1;
	  break;

	case 'n':
	  blipinterval = atoi (optarg);
	  break;

	case 'v':
	  fprintf (stderr, "wp2x version " VERSION " patchlevel %d\n", PATCHLEVEL);
	  errflg++;
	  break;

	default:
	  errflg++;
	  break;
	}
    }

  if (errflg)
    {
    usage:

      fprintf (stderr, "usage: wp2x [-v] [-s][-n X] descriptor input > output\n");
      fprintf (stderr, "       -v for version number\n");
      exit (2);
    }

  blipcount = blipinterval;

  if ((argc - optind) < 2)
    {
      goto usage;
    }

  descriptorFileName = argv[optind];
  fprintf (stderr, "Reading config file: %s\n", descriptorFileName);

  descriptor = dopen (descriptorFileName);
  do_descriptor_file (descriptor);
  fclose (descriptor);

  for (optind++; optind < argc; optind++)
    {
      input = efopen (argv[optind], "rb");
      masterState.infile = input;
      masterState.outfile = stdout;
      if (!process_preamble (&masterState))
	{
	  exit (10);
	}
      if (!process_input (&masterState))
	{
	  exit (11);
	}
      fclose (input);
    }
  return 0;
}
