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

#include "config.h"

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

#include <errno.h>

/************************************************************************/
/* 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);
    fprintf(stderr, " (%s)\n", s, strerror(errno));
    fprintf(stderr, "\n");
    exit(1);
  }

  return fp;
}

/* 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 Cdecl main(int argc, char **argv)
{
  extern char *optarg;
  extern int optind;
  int    errflg=0;
  int    c;
  char   *descriptorFileName;
  state  masterState;
  extern FILE   *dopen(char *);

  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\t -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;
}
