[BACK]Return to roff.c CVS log [TXT][DIR] Up to [cvsweb.bsd.lv] / mandoc

Diff for /mandoc/roff.c between version 1.26 and 1.206

version 1.26, 2008/12/01 09:25:18 version 1.206, 2014/04/08 01:37:27
Line 1 
Line 1 
 /* $Id$ */  /*      $Id$ */
 /*  /*
  * Copyright (c) 2008 Kristaps Dzonsons <kristaps@kth.se>   * Copyright (c) 2010, 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
    * Copyright (c) 2010-2014 Ingo Schwarze <schwarze@openbsd.org>
  *   *
  * Permission to use, copy, modify, and distribute this software for any   * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the   * purpose with or without fee is hereby granted, provided that the above
  * above copyright notice and this permission notice appear in all   * copyright notice and this permission notice appear in all copies.
  * copies.  
  *   *
  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL   * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
  * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED   * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE   * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
  * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL   * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR   * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER   * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR   * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  * PERFORMANCE OF THIS SOFTWARE.  
  */   */
   #ifdef HAVE_CONFIG_H
   #include "config.h"
   #endif
   
 #include <assert.h>  #include <assert.h>
 #include <ctype.h>  #include <ctype.h>
 #include <err.h>  
 #include <stdarg.h>  
 #include <stdlib.h>  
 #include <stdio.h>  #include <stdio.h>
   #include <stdlib.h>
 #include <string.h>  #include <string.h>
 #include <time.h>  
   
 #include "libmdocml.h"  #include "mandoc.h"
 #include "private.h"  #include "mandoc_aux.h"
   #include "libroff.h"
   #include "libmandoc.h"
   
 /* FIXME: warn if Pp occurs before/after Sh etc. (see mdoc.samples). */  /* Maximum number of nested if-else conditionals. */
   #define RSTACK_MAX      128
   
 /* FIXME: warn about "X section only" macros. */  /* Maximum number of string expansions per line, to break infinite loops. */
   #define EXPAND_LIMIT    1000
   
 /* FIXME: warn about empty lists. */  enum    rofft {
           ROFF_ad,
           ROFF_am,
           ROFF_ami,
           ROFF_am1,
           ROFF_as,
           ROFF_cc,
           ROFF_ce,
           ROFF_de,
           ROFF_dei,
           ROFF_de1,
           ROFF_ds,
           ROFF_el,
           ROFF_fam,
           ROFF_hw,
           ROFF_hy,
           ROFF_ie,
           ROFF_if,
           ROFF_ig,
           ROFF_it,
           ROFF_ne,
           ROFF_nh,
           ROFF_nr,
           ROFF_ns,
           ROFF_ps,
           ROFF_rm,
           ROFF_rr,
           ROFF_so,
           ROFF_ta,
           ROFF_tr,
           ROFF_Dd,
           ROFF_TH,
           ROFF_TS,
           ROFF_TE,
           ROFF_T_,
           ROFF_EQ,
           ROFF_EN,
           ROFF_cblock,
           ROFF_USERDEF,
           ROFF_MAX
   };
   
 /* FIXME: roff_layout and roff_text have identical-ish lower bodies. */  /*
    * An incredibly-simple string buffer.
    */
   struct  roffstr {
           char            *p; /* nil-terminated buffer */
           size_t           sz; /* saved strlen(p) */
   };
   
 /* FIXME: NAME section needs specific elements. */  /*
    * A key-value roffstr pair as part of a singly-linked list.
    */
   struct  roffkv {
           struct roffstr   key;
           struct roffstr   val;
           struct roffkv   *next; /* next in list */
   };
   
 #define ROFF_MAXARG       32  /*
    * A single number register as part of a singly-linked list.
    */
   struct  roffreg {
           struct roffstr   key;
           int              val;
           struct roffreg  *next;
   };
   
 enum    roffd {  struct  roff {
         ROFF_ENTER = 0,          struct mparse   *parse; /* parse point */
         ROFF_EXIT          int              options; /* parse options */
           struct roffnode *last; /* leaf of stack */
           int              rstack[RSTACK_MAX]; /* stack of !`ie' rules */
           char             control; /* control character */
           int              rstackpos; /* position in rstack */
           struct roffreg  *regtab; /* number registers */
           struct roffkv   *strtab; /* user-defined strings & macros */
           struct roffkv   *xmbtab; /* multi-byte trans table (`tr') */
           struct roffstr  *xtab; /* single-byte trans table (`tr') */
           const char      *current_string; /* value of last called user macro */
           struct tbl_node *first_tbl; /* first table parsed */
           struct tbl_node *last_tbl; /* last table parsed */
           struct tbl_node *tbl; /* current table being parsed */
           struct eqn_node *last_eqn; /* last equation parsed */
           struct eqn_node *first_eqn; /* first equation parsed */
           struct eqn_node *eqn; /* current equation being parsed */
 };  };
   
 enum    rofftype {  struct  roffnode {
         ROFF_COMMENT,          enum rofft       tok; /* type of node */
         ROFF_TEXT,          struct roffnode *parent; /* up one in stack */
         ROFF_LAYOUT,          int              line; /* parse line */
         ROFF_SPECIAL          int              col; /* parse col */
           char            *name; /* node name, e.g. macro name */
           char            *end; /* end-rules: custom token */
           int              endspan; /* end-rules: next-line or infty */
           int              rule; /* current evaluation rule */
 };  };
   
 #define ROFFCALL_ARGS \  #define ROFF_ARGS        struct roff *r, /* parse ctx */ \
         int tok, struct rofftree *tree, \                           enum rofft tok, /* tok of macro */ \
         char *argv[], enum roffd type                           char **bufp, /* input buffer */ \
                            size_t *szp, /* size of input buffer */ \
                            int ln, /* parse line */ \
                            int ppos, /* original pos in buffer */ \
                            int pos, /* current pos in buffer */ \
                            int *offs /* reset offset of buffer data */
   
 struct  rofftree;  typedef enum rofferr (*roffproc)(ROFF_ARGS);
   
 struct  rofftok {  struct  roffmac {
         int             (*cb)(ROFFCALL_ARGS);   /* Callback. */          const char      *name; /* macro name */
         const int        *args;                 /* Args (or NULL). */          roffproc         proc; /* process new macro */
         const int        *parents;          roffproc         text; /* process as child text of macro */
         const int        *children;          roffproc         sub; /* process as child of macro */
         int               ctx;          int              flags;
         enum rofftype     type;                 /* Type of macro. */  #define ROFFMAC_STRUCT  (1 << 0) /* always interpret */
         int               flags;          struct roffmac  *next;
 #define ROFF_PARSED      (1 << 0)               /* "Parsed". */  
 #define ROFF_CALLABLE    (1 << 1)               /* "Callable". */  
 #define ROFF_SHALLOW     (1 << 2)               /* Nesting block. */  
 };  };
   
 struct  roffarg {  struct  predef {
         int               flags;          const char      *name; /* predefined input name */
 #define ROFF_VALUE       (1 << 0)               /* Has a value. */          const char      *str; /* replacement symbol */
 };  };
   
 struct  roffnode {  #define PREDEF(__name, __str) \
         int               tok;                  /* Token id. */          { (__name), (__str) },
         struct roffnode  *parent;               /* Parent (or NULL). */  
 };  
   
 struct  rofftree {  static  enum rofft       roffhash_find(const char *, size_t);
         struct roffnode  *last;                 /* Last parsed node. */  static  void             roffhash_init(void);
         char             *cur;  static  void             roffnode_cleanscope(struct roff *);
   static  void             roffnode_pop(struct roff *);
   static  void             roffnode_push(struct roff *, enum rofft,
                                   const char *, int, int);
   static  enum rofferr     roff_block(ROFF_ARGS);
   static  enum rofferr     roff_block_text(ROFF_ARGS);
   static  enum rofferr     roff_block_sub(ROFF_ARGS);
   static  enum rofferr     roff_cblock(ROFF_ARGS);
   static  enum rofferr     roff_cc(ROFF_ARGS);
   static  void             roff_ccond(struct roff *, int, int);
   static  enum rofferr     roff_cond(ROFF_ARGS);
   static  enum rofferr     roff_cond_text(ROFF_ARGS);
   static  enum rofferr     roff_cond_sub(ROFF_ARGS);
   static  enum rofferr     roff_ds(ROFF_ARGS);
   static  int              roff_evalcond(const char *, int *);
   static  int              roff_evalnum(const char *, int *, int *, int);
   static  int              roff_evalpar(const char *, int *, int *);
   static  int              roff_evalstrcond(const char *, int *);
   static  void             roff_free1(struct roff *);
   static  void             roff_freereg(struct roffreg *);
   static  void             roff_freestr(struct roffkv *);
   static  char            *roff_getname(struct roff *, char **, int, int);
   static  int              roff_getnum(const char *, int *, int *);
   static  int              roff_getop(const char *, int *, char *);
   static  int              roff_getregn(const struct roff *,
                                   const char *, size_t);
   static  int              roff_getregro(const char *name);
   static  const char      *roff_getstrn(const struct roff *,
                                   const char *, size_t);
   static  enum rofferr     roff_it(ROFF_ARGS);
   static  enum rofferr     roff_line_ignore(ROFF_ARGS);
   static  enum rofferr     roff_nr(ROFF_ARGS);
   static  void             roff_openeqn(struct roff *, const char *,
                                   int, int, const char *);
   static  enum rofft       roff_parse(struct roff *, const char *, int *);
   static  enum rofferr     roff_parsetext(char **, size_t *, int, int *);
   static  enum rofferr     roff_res(struct roff *,
                                   char **, size_t *, int, int);
   static  enum rofferr     roff_rm(ROFF_ARGS);
   static  enum rofferr     roff_rr(ROFF_ARGS);
   static  void             roff_setstr(struct roff *,
                                   const char *, const char *, int);
   static  void             roff_setstrn(struct roffkv **, const char *,
                                   size_t, const char *, size_t, int);
   static  enum rofferr     roff_so(ROFF_ARGS);
   static  enum rofferr     roff_tr(ROFF_ARGS);
   static  enum rofferr     roff_Dd(ROFF_ARGS);
   static  enum rofferr     roff_TH(ROFF_ARGS);
   static  enum rofferr     roff_TE(ROFF_ARGS);
   static  enum rofferr     roff_TS(ROFF_ARGS);
   static  enum rofferr     roff_EQ(ROFF_ARGS);
   static  enum rofferr     roff_EN(ROFF_ARGS);
   static  enum rofferr     roff_T_(ROFF_ARGS);
   static  enum rofferr     roff_userdef(ROFF_ARGS);
   
         time_t            date;                 /* `Dd' results. */  /* See roffhash_find() */
         char              os[64];               /* `Os' results. */  
         char              title[64];            /* `Dt' results. */  
         char              section[64];          /* `Dt' results. */  
         char              volume[64];           /* `Dt' results. */  
   
         int               state;  #define ASCII_HI         126
 #define ROFF_PRELUDE     (1 << 1)               /* In roff prelude. */  #define ASCII_LO         33
 #define ROFF_PRELUDE_Os  (1 << 2)               /* `Os' is parsed. */  #define HASHWIDTH       (ASCII_HI - ASCII_LO + 1)
 #define ROFF_PRELUDE_Dt  (1 << 3)               /* `Dt' is parsed. */  
 #define ROFF_PRELUDE_Dd  (1 << 4)               /* `Dd' is parsed. */  
 #define ROFF_BODY        (1 << 5)               /* In roff body. */  
   
         struct roffcb     cb;  static  struct roffmac  *hash[HASHWIDTH];
         void             *arg;  
   static  struct roffmac   roffs[ROFF_MAX] = {
           { "ad", roff_line_ignore, NULL, NULL, 0, NULL },
           { "am", roff_block, roff_block_text, roff_block_sub, 0, NULL },
           { "ami", roff_block, roff_block_text, roff_block_sub, 0, NULL },
           { "am1", roff_block, roff_block_text, roff_block_sub, 0, NULL },
           { "as", roff_ds, NULL, NULL, 0, NULL },
           { "cc", roff_cc, NULL, NULL, 0, NULL },
           { "ce", roff_line_ignore, NULL, NULL, 0, NULL },
           { "de", roff_block, roff_block_text, roff_block_sub, 0, NULL },
           { "dei", roff_block, roff_block_text, roff_block_sub, 0, NULL },
           { "de1", roff_block, roff_block_text, roff_block_sub, 0, NULL },
           { "ds", roff_ds, NULL, NULL, 0, NULL },
           { "el", roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT, NULL },
           { "fam", roff_line_ignore, NULL, NULL, 0, NULL },
           { "hw", roff_line_ignore, NULL, NULL, 0, NULL },
           { "hy", roff_line_ignore, NULL, NULL, 0, NULL },
           { "ie", roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT, NULL },
           { "if", roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT, NULL },
           { "ig", roff_block, roff_block_text, roff_block_sub, 0, NULL },
           { "it", roff_it, NULL, NULL, 0, NULL },
           { "ne", roff_line_ignore, NULL, NULL, 0, NULL },
           { "nh", roff_line_ignore, NULL, NULL, 0, NULL },
           { "nr", roff_nr, NULL, NULL, 0, NULL },
           { "ns", roff_line_ignore, NULL, NULL, 0, NULL },
           { "ps", roff_line_ignore, NULL, NULL, 0, NULL },
           { "rm", roff_rm, NULL, NULL, 0, NULL },
           { "rr", roff_rr, NULL, NULL, 0, NULL },
           { "so", roff_so, NULL, NULL, 0, NULL },
           { "ta", roff_line_ignore, NULL, NULL, 0, NULL },
           { "tr", roff_tr, NULL, NULL, 0, NULL },
           { "Dd", roff_Dd, NULL, NULL, 0, NULL },
           { "TH", roff_TH, NULL, NULL, 0, NULL },
           { "TS", roff_TS, NULL, NULL, 0, NULL },
           { "TE", roff_TE, NULL, NULL, 0, NULL },
           { "T&", roff_T_, NULL, NULL, 0, NULL },
           { "EQ", roff_EQ, NULL, NULL, 0, NULL },
           { "EN", roff_EN, NULL, NULL, 0, NULL },
           { ".", roff_cblock, NULL, NULL, 0, NULL },
           { NULL, roff_userdef, NULL, NULL, 0, NULL },
 };  };
   
 static  int               roff_Dd(ROFFCALL_ARGS);  /* not currently implemented: Ds em Eq LP Me PP pp Or Rd Sf SH */
 static  int               roff_Dt(ROFFCALL_ARGS);  const   char *const __mdoc_reserved[] = {
 static  int               roff_Os(ROFFCALL_ARGS);          "Ac", "Ad", "An", "Ao", "Ap", "Aq", "Ar", "At",
 #ifdef notyet          "Bc", "Bd", "Bf", "Bk", "Bl", "Bo", "Bq",
 static  int               roff_Ns(ROFFCALL_ARGS);          "Brc", "Bro", "Brq", "Bsx", "Bt", "Bx",
 #endif          "Cd", "Cm", "Db", "Dc", "Dd", "Dl", "Do", "Dq",
           "Dt", "Dv", "Dx", "D1",
           "Ec", "Ed", "Ef", "Ek", "El", "Em",
           "En", "Eo", "Er", "Es", "Ev", "Ex",
           "Fa", "Fc", "Fd", "Fl", "Fn", "Fo", "Fr", "Ft", "Fx",
           "Hf", "Ic", "In", "It", "Lb", "Li", "Lk", "Lp",
           "Ms", "Mt", "Nd", "Nm", "No", "Ns", "Nx",
           "Oc", "Oo", "Op", "Os", "Ot", "Ox",
           "Pa", "Pc", "Pf", "Po", "Pp", "Pq",
           "Qc", "Ql", "Qo", "Qq", "Re", "Rs", "Rv",
           "Sc", "Sh", "Sm", "So", "Sq",
           "Ss", "St", "Sx", "Sy",
           "Ta", "Tn", "Ud", "Ux", "Va", "Vt", "Xc", "Xo", "Xr",
           "%A", "%B", "%C", "%D", "%I", "%J", "%N", "%O",
           "%P", "%Q", "%R", "%T", "%U", "%V",
           NULL
   };
   
 static  int               roff_layout(ROFFCALL_ARGS);  /* not currently implemented: BT DE DS ME MT PT SY TQ YS */
 static  int               roff_text(ROFFCALL_ARGS);  const   char *const __man_reserved[] = {
 static  int               roff_comment(ROFFCALL_ARGS);          "AT", "B", "BI", "BR", "DT",
 static  int               roff_close(ROFFCALL_ARGS);          "EE", "EN", "EQ", "EX", "HP", "I", "IB", "IP", "IR",
           "LP", "OP", "P", "PD", "PP",
           "R", "RB", "RE", "RI", "RS", "SB", "SH", "SM", "SS",
           "TE", "TH", "TP", "TS", "T&", "UC", "UE", "UR",
           NULL
   };
   
 static  struct roffnode  *roffnode_new(int, struct rofftree *);  /* Array of injected predefined strings. */
 static  void              roffnode_free(struct rofftree *);  #define PREDEFS_MAX      38
   static  const struct predef predefs[PREDEFS_MAX] = {
   #include "predefs.in"
   };
   
 static  void              roff_warn(const struct rofftree *,  /* See roffhash_find() */
                                 const char *, char *, ...);  #define ROFF_HASH(p)    (p[0] - ASCII_LO)
 static  void              roff_err(const struct rofftree *,  
                                 const char *, char *, ...);  
   
 static  int               roffscan(int, const int *);  static  int      roffit_lines;  /* number of lines to delay */
 static  int               rofffindtok(const char *);  static  char    *roffit_macro;  /* nil-terminated macro line */
 static  int               rofffindarg(const char *);  
 static  int               rofffindcallable(const char *);  
 static  int               roffargs(const struct rofftree *,  
                                 int, char *, char **);  
 static  int               roffargok(int, int);  
 static  int               roffnextopt(const struct rofftree *,  
                                 int, char ***, char **);  
 static  int               roffparse(struct rofftree *, char *);  
 static  int               textparse(const struct rofftree *, char *);  
   
   static void
   roffhash_init(void)
   {
           struct roffmac   *n;
           int               buc, i;
   
 static  const int roffarg_An[] = { ROFF_Split, ROFF_Nosplit,          for (i = 0; i < (int)ROFF_USERDEF; i++) {
         ROFF_ARGMAX };                  assert(roffs[i].name[0] >= ASCII_LO);
 static  const int roffarg_Bd[] = { ROFF_Ragged, ROFF_Unfilled,                  assert(roffs[i].name[0] <= ASCII_HI);
         ROFF_Literal, ROFF_File, ROFF_Offset, ROFF_Filled,  
         ROFF_Compact, ROFF_ARGMAX };  
 static  const int roffarg_Bk[] = { ROFF_Words, ROFF_ARGMAX };  
 static  const int roffarg_Ex[] = { ROFF_Std, ROFF_ARGMAX };  
 static  const int roffarg_Rv[] = { ROFF_Std, ROFF_ARGMAX };  
 static  const int roffarg_Bl[] = { ROFF_Bullet, ROFF_Dash,  
         ROFF_Hyphen, ROFF_Item, ROFF_Enum, ROFF_Tag, ROFF_Diag,  
         ROFF_Hang, ROFF_Ohang, ROFF_Inset, ROFF_Column, ROFF_Offset,  
         ROFF_Width, ROFF_Compact, ROFF_ARGMAX };  
 static  const int roffarg_St[] = {  
         ROFF_p1003_1_88, ROFF_p1003_1_90, ROFF_p1003_1_96,  
         ROFF_p1003_1_2001, ROFF_p1003_1_2004, ROFF_p1003_1,  
         ROFF_p1003_1b, ROFF_p1003_1b_93, ROFF_p1003_1c_95,  
         ROFF_p1003_1g_2000, ROFF_p1003_2_92, ROFF_p1387_2_95,  
         ROFF_p1003_2, ROFF_p1387_2, ROFF_isoC_90, ROFF_isoC_amd1,  
         ROFF_isoC_tcor1, ROFF_isoC_tcor2, ROFF_isoC_99, ROFF_ansiC,  
         ROFF_ansiC_89, ROFF_ansiC_99, ROFF_ieee754, ROFF_iso8802_3,  
         ROFF_xpg3, ROFF_xpg4, ROFF_xpg4_2, ROFF_xpg4_3, ROFF_xbd5,  
         ROFF_xcu5, ROFF_xsh5, ROFF_xns5, ROFF_xns5_2d2_0,  
         ROFF_xcurses4_2, ROFF_susv2, ROFF_susv3, ROFF_svid4,  
         ROFF_ARGMAX };  
   
 static  const int roffchild_Bl[] = { ROFF_It, ROFF_El, ROFF_MAX };                  buc = ROFF_HASH(roffs[i].name);
 static  const int roffchild_Fo[] = { ROFF_Fa, ROFF_Fc, ROFF_MAX };  
 static  const int roffchild_Oo[] = { ROFF_Op, ROFF_Oc, ROFF_MAX };  
 static  const int roffchild_Rs[] = { ROFF_Re, ROFF__A, ROFF__B,  
         ROFF__D, ROFF__I, ROFF__J, ROFF__N, ROFF__O, ROFF__P,  
         ROFF__R, ROFF__T, ROFF__V, ROFF_MAX };  
   
 static  const int roffparent_El[] = { ROFF_Bl, ROFF_It, ROFF_MAX };                  if (NULL != (n = hash[buc])) {
 static  const int roffparent_Fc[] = { ROFF_Fo, ROFF_Fa, ROFF_MAX };                          for ( ; n->next; n = n->next)
 static  const int roffparent_Oc[] = { ROFF_Oo, ROFF_Oc, ROFF_MAX };                                  /* Do nothing. */ ;
 static  const int roffparent_It[] = { ROFF_Bl, ROFF_It, ROFF_MAX };                          n->next = &roffs[i];
 static  const int roffparent_Re[] = { ROFF_Rs, ROFF_MAX };                  } else
                           hash[buc] = &roffs[i];
           }
   }
   
 /* Table of all known tokens. */  /*
 static  const struct rofftok tokens[ROFF_MAX] = {   * Look up a roff token by its name.  Returns ROFF_MAX if no macro by
         {roff_comment, NULL, NULL, NULL, 0, ROFF_COMMENT, 0 }, /* \" */   * the nil-terminated string name could be found.
         {     roff_Dd, NULL, NULL, NULL, 0, ROFF_TEXT, 0 }, /* Dd */   */
         {     roff_Dt, NULL, NULL, NULL, 0, ROFF_TEXT, 0 }, /* Dt */  static enum rofft
         {     roff_Os, NULL, NULL, NULL, 0, ROFF_TEXT, 0 }, /* Os */  roffhash_find(const char *p, size_t s)
         { roff_layout, NULL, NULL, NULL, ROFF_Sh, ROFF_LAYOUT, 0 }, /* Sh */  {
         { roff_layout, NULL, NULL, NULL, ROFF_Ss, ROFF_LAYOUT, 0 }, /* Ss */          int              buc;
         {   roff_text, NULL, NULL, NULL, ROFF_Pp, ROFF_TEXT, 0 }, /* Pp */          struct roffmac  *n;
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED }, /* D1 */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED }, /* Dl */  
         { roff_layout, roffarg_Bd, NULL, NULL, 0, ROFF_LAYOUT, 0 },     /* Bd */  
         {  roff_close, NULL, NULL, NULL, ROFF_Bd, ROFF_LAYOUT, 0 }, /* Ed */  
         { roff_layout, roffarg_Bl, NULL, roffchild_Bl, 0, ROFF_LAYOUT, 0 }, /* Bl */  
         {  roff_close, NULL, roffparent_El, NULL, ROFF_Bl, ROFF_LAYOUT, 0 }, /* El */  
         { roff_layout, NULL, roffparent_It, NULL, ROFF_It, ROFF_LAYOUT, ROFF_PARSED | ROFF_SHALLOW }, /* It */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Ad */ /* FIXME */  
         {   roff_text, roffarg_An, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED }, /* An */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Ar */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, 0 }, /* Cd */ /* XXX man.4 only */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Cm */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Dv */ /* XXX needs arg */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Er */ /* XXX needs arg */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Ev */ /* XXX needs arg */  
         {   roff_text, roffarg_Ex, NULL, NULL, 0, ROFF_TEXT, 0 }, /* Ex */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Fa */ /* XXX needs arg */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, 0 }, /* Fd */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Fl */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Fn */ /* XXX needs arg */ /* FIXME */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED }, /* Ft */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Ic */ /* XXX needs arg */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, 0 }, /* In */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Li */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, 0 }, /* Nd */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Nm */ /* FIXME */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Op */  
         {   NULL, NULL, NULL, NULL, 0, ROFF_TEXT, 0 }, /* Ot */ /* XXX deprecated */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Pa */  
         {   roff_text, roffarg_Rv, NULL, NULL, 0, ROFF_TEXT, 0 }, /* Rv */  
         {   roff_text, roffarg_St, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* St */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Va */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Vt */ /* XXX needs arg */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Xr */ /* XXX needs arg */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED }, /* %A */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE}, /* %B */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, 0 }, /* %D */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE}, /* %I */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE}, /* %J */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, 0 }, /* %N */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, 0 }, /* %O */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, 0 }, /* %P */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, 0 }, /* %R */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED }, /* %T */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, 0 }, /* %V */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Ac */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Ao */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Aq */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, 0 }, /* At */ /* XXX at most 2 args */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Bc */  
         {   NULL, NULL, NULL, NULL, 0, ROFF_TEXT, 0 },  /* Bf */ /* FIXME */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Bo */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Bq */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED }, /* Bsx */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED }, /* Bx */  
         {        NULL, NULL, NULL, NULL, 0, ROFF_SPECIAL, 0 },  /* Db */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Dc */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Do */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Dq */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Ec */  
         {   NULL, NULL, NULL, NULL, 0, ROFF_TEXT, 0 },  /* Ef */ /* FIXME */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Em */ /* XXX needs arg */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Eo */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED }, /* Fx */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED }, /* Ms */  
         {   NULL, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* No */  
         {        NULL, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Ns */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED }, /* Nx */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED }, /* Ox */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Pc */  
         {        NULL, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED }, /* Pf */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_LAYOUT, ROFF_PARSED | ROFF_CALLABLE }, /* Po */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Pq */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Qc */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Ql */  
         { roff_layout, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Qo */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Qq */  
         {  roff_close, NULL, roffparent_Re, NULL, ROFF_Rs, ROFF_LAYOUT, 0 }, /* Re */  
         { roff_layout, NULL, NULL, roffchild_Rs, 0, ROFF_LAYOUT, 0 },   /* Rs */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Sc */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* So */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Sq */  
         {        NULL, NULL, NULL, NULL, 0, ROFF_SPECIAL, 0 }, /* Sm */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Sx */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Sy */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Tn */  
         {   roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED }, /* Ux */  
         {   NULL, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Xc */  
         {   NULL, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Xo */  
         { roff_layout, NULL, NULL, roffchild_Fo, 0, ROFF_LAYOUT, 0 }, /* Fo */  
         {  roff_close, NULL, roffparent_Fc, NULL, ROFF_Fo, ROFF_LAYOUT, 0 }, /* Fc */  
         { roff_layout, NULL, NULL, roffchild_Oo, 0, ROFF_LAYOUT, 0 }, /* Oo */  
         {  roff_close, NULL, roffparent_Oc, NULL, ROFF_Oo, ROFF_LAYOUT, 0 }, /* Oc */  
         { roff_layout, roffarg_Bk, NULL, NULL, 0, ROFF_LAYOUT, 0 }, /* Bk */  
         {  roff_close, NULL, NULL, NULL, ROFF_Bk, ROFF_LAYOUT, 0 }, /* Ek */  
         };  
   
 /* Table of all known token arguments. */          /*
 static  const int tokenargs[ROFF_ARGMAX] = {           * libroff has an extremely simple hashtable, for the time
         0,              0,              0,              0,           * being, which simply keys on the first character, which must
         0,              ROFF_VALUE,     ROFF_VALUE,     0,           * be printable, then walks a chain.  It works well enough until
         0,              0,              0,              0,           * optimised.
         0,              0,              0,              0,           */
         0,              0,              ROFF_VALUE,     0,  
         0,              0,              0,              0,  
         0,              0,              0,              0,  
         0,              0,              0,              0,  
         0,              0,              0,              0,  
         0,              0,              0,              0,  
         0,              0,              0,              0,  
         0,              0,              0,              0,  
         0,              0,              0,              0,  
         0,              0,              0,              0,  
         0,              0,              0,              0,  
         };  
   
 const   char *const toknamesp[ROFF_MAX] = {          if (p[0] < ASCII_LO || p[0] > ASCII_HI)
         "\\\"",         "Dd",           "Dt",           "Os",                  return(ROFF_MAX);
         "Sh",           "Ss",           "Pp",           "D1",  
         "Dl",           "Bd",           "Ed",           "Bl",  
         "El",           "It",           "Ad",           "An",  
         "Ar",           "Cd",           "Cm",           "Dv",  
         "Er",           "Ev",           "Ex",           "Fa",  
         "Fd",           "Fl",           "Fn",           "Ft",  
         "Ic",           "In",           "Li",           "Nd",  
         "Nm",           "Op",           "Ot",           "Pa",  
         "Rv",           "St",           "Va",           "Vt",  
         /* LINTED */  
         "Xr",           "\%A",          "\%B",          "\%D",  
         /* LINTED */  
         "\%I",          "\%J",          "\%N",          "\%O",  
         /* LINTED */  
         "\%P",          "\%R",          "\%T",          "\%V",  
         "Ac",           "Ao",           "Aq",           "At",  
         "Bc",           "Bf",           "Bo",           "Bq",  
         "Bsx",          "Bx",           "Db",           "Dc",  
         "Do",           "Dq",           "Ec",           "Ef",  
         "Em",           "Eo",           "Fx",           "Ms",  
         "No",           "Ns",           "Nx",           "Ox",  
         "Pc",           "Pf",           "Po",           "Pq",  
         "Qc",           "Ql",           "Qo",           "Qq",  
         "Re",           "Rs",           "Sc",           "So",  
         "Sq",           "Sm",           "Sx",           "Sy",  
         "Tn",           "Ux",           "Xc",           "Xo",  
         "Fo",           "Fc",           "Oo",           "Oc",  
         "Bk",           "Ek",  
         };  
   
 const   char *const tokargnamesp[ROFF_ARGMAX] = {          buc = ROFF_HASH(p);
         "split",                "nosplit",              "ragged",  
         "unfilled",             "literal",              "file",  
         "offset",               "bullet",               "dash",  
         "hyphen",               "item",                 "enum",  
         "tag",                  "diag",                 "hang",  
         "ohang",                "inset",                "column",  
         "width",                "compact",              "std",  
         "p1003.1-88",           "p1003.1-90",           "p1003.1-96",  
         "p1003.1-2001",         "p1003.1-2004",         "p1003.1",  
         "p1003.1b",             "p1003.1b-93",          "p1003.1c-95",  
         "p1003.1g-2000",        "p1003.2-92",           "p1387.2-95",  
         "p1003.2",              "p1387.2",              "isoC-90",  
         "isoC-amd1",            "isoC-tcor1",           "isoC-tcor2",  
         "isoC-99",              "ansiC",                "ansiC-89",  
         "ansiC-99",             "ieee754",              "iso8802-3",  
         "xpg3",                 "xpg4",                 "xpg4.2",  
         "xpg4.3",               "xbd5",                 "xcu5",  
         "xsh5",                 "xns5",                 "xns5.2d2.0",  
         "xcurses4.2",           "susv2",                "susv3",  
         "svid4",                "filled",               "words",  
         };  
   
 const   char *const *toknames = toknamesp;          if (NULL == (n = hash[buc]))
 const   char *const *tokargnames = tokargnamesp;                  return(ROFF_MAX);
           for ( ; n; n = n->next)
                   if (0 == strncmp(n->name, p, s) && '\0' == n->name[(int)s])
                           return((enum rofft)(n - roffs));
   
           return(ROFF_MAX);
   }
   
 int  
 roff_free(struct rofftree *tree, int flush)  /*
    * Pop the current node off of the stack of roff instructions currently
    * pending.
    */
   static void
   roffnode_pop(struct roff *r)
 {  {
         int              error, t;          struct roffnode *p;
         struct roffnode *n;  
   
         error = 0;          assert(r->last);
           p = r->last;
   
         if ( ! flush)          r->last = r->last->parent;
                 goto end;          free(p->name);
           free(p->end);
           free(p);
   }
   
         error = 1;  
   
         if (ROFF_PRELUDE & tree->state) {  /*
                 roff_warn(tree, NULL, "prelude never finished");   * Push a roff node onto the instruction stack.  This must later be
                 goto end;   * removed with roffnode_pop().
         }   */
   static void
   roffnode_push(struct roff *r, enum rofft tok, const char *name,
                   int line, int col)
   {
           struct roffnode *p;
   
         for (n = tree->last; n->parent; n = n->parent) {          p = mandoc_calloc(1, sizeof(struct roffnode));
                 if (0 != tokens[n->tok].ctx)          p->tok = tok;
                         continue;          if (name)
                 roff_warn(tree, NULL, "closing explicit scope `%s'",                  p->name = mandoc_strdup(name);
                                 toknames[n->tok]);          p->parent = r->last;
                 goto end;          p->line = line;
           p->col = col;
           p->rule = p->parent ? p->parent->rule : 0;
   
           r->last = p;
   }
   
   
   static void
   roff_free1(struct roff *r)
   {
           struct tbl_node *tbl;
           struct eqn_node *e;
           int              i;
   
           while (NULL != (tbl = r->first_tbl)) {
                   r->first_tbl = tbl->next;
                   tbl_free(tbl);
         }          }
   
         while (tree->last) {          r->first_tbl = r->last_tbl = r->tbl = NULL;
                 t = tree->last->tok;  
                 if ( ! (*tokens[t].cb)(t, tree, NULL, ROFF_EXIT))          while (NULL != (e = r->first_eqn)) {
                         goto end;                  r->first_eqn = e->next;
                   eqn_free(e);
         }          }
   
         error = 0;          r->first_eqn = r->last_eqn = r->eqn = NULL;
   
 end:          while (r->last)
                   roffnode_pop(r);
   
         while (tree->last)          roff_freestr(r->strtab);
                 roffnode_free(tree);          roff_freestr(r->xmbtab);
   
         free(tree);          r->strtab = r->xmbtab = NULL;
   
         return(error ? 0 : 1);          roff_freereg(r->regtab);
   
           r->regtab = NULL;
   
           if (r->xtab)
                   for (i = 0; i < 128; i++)
                           free(r->xtab[i].p);
   
           free(r->xtab);
           r->xtab = NULL;
 }  }
   
   void
   roff_reset(struct roff *r)
   {
   
 struct rofftree *          roff_free1(r);
 roff_alloc(const struct roffcb *cb, void *args)          r->control = 0;
   }
   
   
   void
   roff_free(struct roff *r)
 {  {
         struct rofftree *tree;  
   
         assert(args);          roff_free1(r);
         assert(cb);          free(r);
   }
   
         if (NULL == (tree = calloc(1, sizeof(struct rofftree))))  
                 err(1, "calloc");  
   
         tree->state = ROFF_PRELUDE;  struct roff *
         tree->arg = args;  roff_alloc(struct mparse *parse, int options)
   {
           struct roff     *r;
   
         (void)memcpy(&tree->cb, cb, sizeof(struct roffcb));          r = mandoc_calloc(1, sizeof(struct roff));
           r->parse = parse;
           r->options = options;
           r->rstackpos = -1;
   
           roffhash_init();
   
         return(tree);          return(r);
 }  }
   
   /*
 int   * In the current line, expand escape sequences that tend to get
 roff_engine(struct rofftree *tree, char *buf)   * used in numerical expressions and conditional requests.
    * Also check the syntax of the remaining escape sequences.
    */
   static enum rofferr
   roff_res(struct roff *r, char **bufp, size_t *szp, int ln, int pos)
 {  {
           char             ubuf[12]; /* buffer to print the number */
           const char      *start; /* start of the string to process */
           const char      *stesc; /* start of an escape sequence ('\\') */
           const char      *stnam; /* start of the name, after "[(*" */
           const char      *cp;    /* end of the name, e.g. before ']' */
           const char      *res;   /* the string to be substituted */
           char            *nbuf;  /* new buffer to copy bufp to */
           size_t           maxl;  /* expected length of the escape name */
           size_t           naml;  /* actual length of the escape name */
           size_t           ressz; /* size of the replacement string */
           int              expand_count;  /* to avoid infinite loops */
           int              npos;  /* position in numeric expression */
           int              irc;   /* return code from roff_evalnum() */
           char             term;  /* character terminating the escape */
   
         tree->cur = buf;          expand_count = 0;
         assert(buf);          start = *bufp + pos;
           stesc = strchr(start, '\0') - 1;
           while (stesc-- > start) {
   
         if (0 == *buf) {                  /* Search backwards for the next backslash. */
                 roff_warn(tree, buf, "blank line");  
                 return(0);  
         } else if ('.' != *buf)  
                 return(textparse(tree, buf));  
   
         return(roffparse(tree, buf));                  if ('\\' != *stesc)
 }                          continue;
   
                   /* If it is escaped, skip it. */
   
 static int                  for (cp = stesc - 1; cp >= start; cp--)
 textparse(const struct rofftree *tree, char *buf)                          if ('\\' != *cp)
 {                                  break;
   
         return((*tree->cb.roffdata)(tree->arg, 1, buf));                  if (0 == (stesc - cp) % 2) {
 }                          stesc = cp;
                           continue;
                   }
   
                   /* Decide whether to expand or to check only. */
   
 static int                  term = '\0';
 roffargs(const struct rofftree *tree,                  cp = stesc + 1;
                 int tok, char *buf, char **argv)                  switch (*cp) {
                   case ('*'):
                           res = NULL;
                           break;
                   case ('B'):
                           /* FALLTHROUGH */
                   case ('w'):
                           term = cp[1];
                           /* FALLTHROUGH */
                   case ('n'):
                           res = ubuf;
                           break;
                   default:
                           if (ESCAPE_ERROR == mandoc_escape(&cp, NULL, NULL))
                                   mandoc_msg(MANDOCERR_BADESCAPE, r->parse,
                                       ln, (int)(stesc - *bufp), NULL);
                           continue;
                   }
   
                   if (EXPAND_LIMIT < ++expand_count) {
                           mandoc_msg(MANDOCERR_ROFFLOOP, r->parse,
                               ln, (int)(stesc - *bufp), NULL);
                           return(ROFF_IGN);
                   }
   
                   /*
                    * The third character decides the length
                    * of the name of the string or register.
                    * Save a pointer to the name.
                    */
   
                   if ('\0' == term) {
                           switch (*++cp) {
                           case ('\0'):
                                   maxl = 0;
                                   break;
                           case ('('):
                                   cp++;
                                   maxl = 2;
                                   break;
                           case ('['):
                                   cp++;
                                   term = ']';
                                   maxl = 0;
                                   break;
                           default:
                                   maxl = 1;
                                   break;
                           }
                   } else {
                           cp += 2;
                           maxl = 0;
                   }
                   stnam = cp;
   
                   /* Advance to the end of the name. */
   
                   for (naml = 0; 0 == maxl || naml < maxl; naml++, cp++) {
                           if ('\0' == *cp) {
                                   mandoc_msg
                                           (MANDOCERR_BADESCAPE,
                                            r->parse, ln,
                                            (int)(stesc - *bufp), NULL);
                                   break;
                           }
                           if (0 == maxl && *cp == term) {
                                   cp++;
                                   break;
                           }
                   }
   
                   /*
                    * Retrieve the replacement string; if it is
                    * undefined, resume searching for escapes.
                    */
   
                   switch (stesc[1]) {
                   case ('*'):
                           res = roff_getstrn(r, stnam, naml);
                           break;
                   case ('B'):
                           npos = 0;
                           irc = roff_evalnum(stnam, &npos, NULL, 0);
                           ubuf[0] = irc && stnam + npos + 1 == cp
                               ? '1' : '0';
                           ubuf[1] = '\0';
                           break;
                   case ('n'):
                           snprintf(ubuf, sizeof(ubuf), "%d",
                               roff_getregn(r, stnam, naml));
                           break;
                   case ('w'):
                           snprintf(ubuf, sizeof(ubuf), "%d",
                               24 * (int)naml);
                           break;
                   }
   
                   if (NULL == res) {
                           mandoc_msg
                                   (MANDOCERR_BADESCAPE, r->parse,
                                    ln, (int)(stesc - *bufp), NULL);
                           res = "";
                   }
                   ressz = strlen(res);
   
                   /* Replace the escape sequence by the string. */
   
                   *szp += ressz + 1;
                   nbuf = mandoc_malloc(*szp);
   
                   strlcpy(nbuf, *bufp, (size_t)(stesc - *bufp + 1));
                   strlcat(nbuf, res, *szp);
                   strlcat(nbuf, cp, *szp);
   
                   /* Prepare for the next replacement. */
   
                   start = nbuf + pos;
                   stesc = nbuf + (stesc - *bufp) + ressz;
                   free(*bufp);
                   *bufp = nbuf;
           }
           return(ROFF_CONT);
   }
   
   /*
    * Process text streams:
    * Convert all breakable hyphens into ASCII_HYPH.
    * Decrement and spring input line trap.
    */
   static enum rofferr
   roff_parsetext(char **bufp, size_t *szp, int pos, int *offs)
 {  {
         int              i;          size_t           sz;
           const char      *start;
         char            *p;          char            *p;
           int              isz;
           enum mandoc_esc  esc;
   
         assert(tok >= 0 && tok < ROFF_MAX);          start = p = *bufp + pos;
         assert('.' == *buf);  
   
         p = buf;          while ('\0' != *p) {
                   sz = strcspn(p, "-\\");
                   p += sz;
   
         /* LINTED */                  if ('\0' == *p)
         for (i = 0; *buf && i < ROFF_MAXARG; i++) {                          break;
                 if ('\"' == *buf) {  
                         argv[i] = ++buf;                  if ('\\' == *p) {
                         while (*buf && '\"' != *buf)                          /* Skip over escapes. */
                                 buf++;                          p++;
                         if (0 == *buf) {                          esc = mandoc_escape((const char **)&p, NULL, NULL);
                                 roff_err(tree, argv[i], "unclosed "                          if (ESCAPE_ERROR == esc)
                                                 "quote in argument "                                  break;
                                                 "list for `%s'",                          continue;
                                                 toknames[tok]);                  } else if (p == start) {
                                 return(0);                          p++;
                         }                          continue;
                 } else {  
                         argv[i] = buf++;  
                         while (*buf && ! isspace(*buf))  
                                 buf++;  
                         if (0 == *buf)  
                                 continue;  
                 }                  }
                 *buf++ = 0;  
                 while (*buf && isspace(*buf))                  if (isalpha((unsigned char)p[-1]) &&
                         buf++;                      isalpha((unsigned char)p[1]))
                           *p = ASCII_HYPH;
                   p++;
         }          }
   
         assert(i > 0);          /* Spring the input line trap. */
         if (ROFF_MAXARG == i && *buf) {          if (1 == roffit_lines) {
                 roff_err(tree, p, "too many arguments for `%s'", toknames                  isz = mandoc_asprintf(&p, "%s\n.%s", *bufp, roffit_macro);
                                 [tok]);                  free(*bufp);
                 return(0);                  *bufp = p;
                   *szp = isz + 1;
                   *offs = 0;
                   free(roffit_macro);
                   roffit_lines = 0;
                   return(ROFF_REPARSE);
           } else if (1 < roffit_lines)
                   --roffit_lines;
           return(ROFF_CONT);
   }
   
   enum rofferr
   roff_parseln(struct roff *r, int ln, char **bufp,
                   size_t *szp, int pos, int *offs)
   {
           enum rofft       t;
           enum rofferr     e;
           int              ppos, ctl;
   
           /*
            * Run the reserved-word filter only if we have some reserved
            * words to fill in.
            */
   
           e = roff_res(r, bufp, szp, ln, pos);
           if (ROFF_IGN == e)
                   return(e);
           assert(ROFF_CONT == e);
   
           ppos = pos;
           ctl = roff_getcontrol(r, *bufp, &pos);
   
           /*
            * First, if a scope is open and we're not a macro, pass the
            * text through the macro's filter.  If a scope isn't open and
            * we're not a macro, just let it through.
            * Finally, if there's an equation scope open, divert it into it
            * no matter our state.
            */
   
           if (r->last && ! ctl) {
                   t = r->last->tok;
                   assert(roffs[t].text);
                   e = (*roffs[t].text)
                           (r, t, bufp, szp, ln, pos, pos, offs);
                   assert(ROFF_IGN == e || ROFF_CONT == e);
                   if (ROFF_CONT != e)
                           return(e);
         }          }
           if (r->eqn)
                   return(eqn_read(&r->eqn, ln, *bufp, ppos, offs));
           if ( ! ctl) {
                   if (r->tbl)
                           return(tbl_read(r->tbl, ln, *bufp, pos));
                   return(roff_parsetext(bufp, szp, pos, offs));
           }
   
         argv[i] = NULL;          /*
         return(1);           * If a scope is open, go to the child handler for that macro,
            * as it may want to preprocess before doing anything with it.
            * Don't do so if an equation is open.
            */
   
           if (r->last) {
                   t = r->last->tok;
                   assert(roffs[t].sub);
                   return((*roffs[t].sub)
                                   (r, t, bufp, szp,
                                    ln, ppos, pos, offs));
           }
   
           /*
            * Lastly, as we've no scope open, try to look up and execute
            * the new macro.  If no macro is found, simply return and let
            * the compilers handle it.
            */
   
           if (ROFF_MAX == (t = roff_parse(r, *bufp, &pos)))
                   return(ROFF_CONT);
   
           assert(roffs[t].proc);
           return((*roffs[t].proc)
                           (r, t, bufp, szp,
                            ln, ppos, pos, offs));
 }  }
   
   
 static int  void
 roffscan(int tok, const int *tokv)  roff_endparse(struct roff *r)
 {  {
   
         if (NULL == tokv)          if (r->last)
                 return(1);                  mandoc_msg(MANDOCERR_SCOPEEXIT, r->parse,
                                   r->last->line, r->last->col, NULL);
   
         for ( ; ROFF_MAX != *tokv; tokv++)          if (r->eqn) {
                 if (tok == *tokv)                  mandoc_msg(MANDOCERR_SCOPEEXIT, r->parse,
                         return(1);                                  r->eqn->eqn.ln, r->eqn->eqn.pos, NULL);
                   eqn_end(&r->eqn);
           }
   
         return(0);          if (r->tbl) {
                   mandoc_msg(MANDOCERR_SCOPEEXIT, r->parse,
                                   r->tbl->line, r->tbl->pos, NULL);
                   tbl_end(&r->tbl);
           }
 }  }
   
   /*
    * Parse a roff node's type from the input buffer.  This must be in the
    * form of ".foo xxx" in the usual way.
    */
   static enum rofft
   roff_parse(struct roff *r, const char *buf, int *pos)
   {
           const char      *mac;
           size_t           maclen;
           enum rofft       t;
   
 static int          if ('\0' == buf[*pos] || '"' == buf[*pos] ||
 roffparse(struct rofftree *tree, char *buf)                          '\t' == buf[*pos] || ' ' == buf[*pos])
                   return(ROFF_MAX);
   
           /* We stop the macro parse at an escape, tab, space, or nil. */
   
           mac = buf + *pos;
           maclen = strcspn(mac, " \\\t\0");
   
           t = (r->current_string = roff_getstrn(r, mac, maclen))
               ? ROFF_USERDEF : roffhash_find(mac, maclen);
   
           *pos += (int)maclen;
   
           while (buf[*pos] && ' ' == buf[*pos])
                   (*pos)++;
   
           return(t);
   }
   
   /* ARGSUSED */
   static enum rofferr
   roff_cblock(ROFF_ARGS)
 {  {
         int               tok, t;  
         struct roffnode  *n;  
         char             *argv[ROFF_MAXARG];  
         char            **argvp;  
   
         if (0 != *buf && 0 != *(buf + 1) && 0 != *(buf + 2))          /*
                 if (0 == strncmp(buf, ".\\\"", 3))           * A block-close `..' should only be invoked as a child of an
                         return(1);           * ignore macro, otherwise raise a warning and just ignore it.
            */
   
         if (ROFF_MAX == (tok = rofffindtok(buf + 1))) {          if (NULL == r->last) {
                 roff_err(tree, buf + 1, "bogus line macro");                  mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
                 return(0);                  return(ROFF_IGN);
         } else if (NULL == tokens[tok].cb) {  
                 roff_err(tree, buf + 1, "unsupported macro `%s'",  
                                 toknames[tok]);  
                 return(0);  
         }          }
   
         assert(ROFF___ != tok);          switch (r->last->tok) {
         if ( ! roffargs(tree, tok, buf, argv))          case (ROFF_am):
                 return(0);                  /* FALLTHROUGH */
           case (ROFF_ami):
                   /* FALLTHROUGH */
           case (ROFF_am1):
                   /* FALLTHROUGH */
           case (ROFF_de):
                   /* ROFF_de1 is remapped to ROFF_de in roff_block(). */
                   /* FALLTHROUGH */
           case (ROFF_dei):
                   /* FALLTHROUGH */
           case (ROFF_ig):
                   break;
           default:
                   mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
                   return(ROFF_IGN);
           }
   
         argvp = (char **)argv;          if ((*bufp)[pos])
                   mandoc_msg(MANDOCERR_ARGSLOST, r->parse, ln, pos, NULL);
   
         /*          roffnode_pop(r);
          * Prelude macros break some assumptions, so branch now.          roffnode_cleanscope(r);
          */          return(ROFF_IGN);
   
         if (ROFF_PRELUDE & tree->state) {  
                 assert(NULL == tree->last);  
                 return((*tokens[tok].cb)(tok, tree, argvp, ROFF_ENTER));  
         } else  
                 assert(tree->last);  
   
         assert(ROFF_BODY & tree->state);  }
   
         /*  
          * First check that our possible parents and parent's possible  
          * children are satisfied.  
          */  
   
         if ( ! roffscan(tree->last->tok, tokens[tok].parents)) {  static void
                 roff_err(tree, *argvp, "`%s' has invalid parent `%s'",  roffnode_cleanscope(struct roff *r)
                                 toknames[tok],  {
                                 toknames[tree->last->tok]);  
                 return(0);  
         }  
   
         if ( ! roffscan(tok, tokens[tree->last->tok].children)) {          while (r->last) {
                 roff_err(tree, *argvp, "`%s' is invalid child of `%s'",                  if (--r->last->endspan != 0)
                                 toknames[tok],                          break;
                                 toknames[tree->last->tok]);                  roffnode_pop(r);
                 return(0);  
         }          }
   }
   
   
   static void
   roff_ccond(struct roff *r, int ln, int ppos)
   {
   
           if (NULL == r->last) {
                   mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
                   return;
           }
   
           switch (r->last->tok) {
           case (ROFF_el):
                   /* FALLTHROUGH */
           case (ROFF_ie):
                   /* FALLTHROUGH */
           case (ROFF_if):
                   break;
           default:
                   mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
                   return;
           }
   
           if (r->last->endspan > -1) {
                   mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
                   return;
           }
   
           roffnode_pop(r);
           roffnode_cleanscope(r);
           return;
   }
   
   
   /* ARGSUSED */
   static enum rofferr
   roff_block(ROFF_ARGS)
   {
           int             sv;
           size_t          sz;
           char            *name;
   
           name = NULL;
   
           if (ROFF_ig != tok) {
                   if ('\0' == (*bufp)[pos]) {
                           mandoc_msg(MANDOCERR_NOARGS, r->parse, ln, ppos, NULL);
                           return(ROFF_IGN);
                   }
   
                   /*
                    * Re-write `de1', since we don't really care about
                    * groff's strange compatibility mode, into `de'.
                    */
   
                   if (ROFF_de1 == tok)
                           tok = ROFF_de;
                   if (ROFF_de == tok)
                           name = *bufp + pos;
                   else
                           mandoc_msg(MANDOCERR_REQUEST, r->parse, ln, ppos,
                               roffs[tok].name);
   
                   while ((*bufp)[pos] && ! isspace((unsigned char)(*bufp)[pos]))
                           pos++;
   
                   while (isspace((unsigned char)(*bufp)[pos]))
                           (*bufp)[pos++] = '\0';
           }
   
           roffnode_push(r, tok, name, ln, ppos);
   
         /*          /*
          * Branch if we're not a layout token.           * At the beginning of a `de' macro, clear the existing string
            * with the same name, if there is one.  New content will be
            * appended from roff_block_text() in multiline mode.
          */           */
   
         if (ROFF_LAYOUT != tokens[tok].type)          if (ROFF_de == tok)
                 return((*tokens[tok].cb)(tok, tree, argvp, ROFF_ENTER));                  roff_setstr(r, name, "", 0);
   
         /*          if ('\0' == (*bufp)[pos])
          * Check our scope rules.                  return(ROFF_IGN);
          */  
   
         if (0 == tokens[tok].ctx)          /* If present, process the custom end-of-line marker. */
                 return((*tokens[tok].cb)(tok, tree, argvp, ROFF_ENTER));  
   
           sv = pos;
           while ((*bufp)[pos] && ! isspace((unsigned char)(*bufp)[pos]))
                   pos++;
   
         /*          /*
          * First consider implicit-end tags, like as follows:           * Note: groff does NOT like escape characters in the input.
          *      .Sh SECTION 1           * Instead of detecting this, we're just going to let it fly and
          *      .Sh SECTION 2           * to hell with it.
          * In this, we want to close the scope of the NAME section.  If  
          * there's an intermediary implicit-end tag, such as  
          *      .Sh SECTION 1  
          *      .Ss Subsection 1  
          *      .Sh SECTION 2  
          * then it must be closed as well.  
          */           */
   
         if (tok == tokens[tok].ctx) {          assert(pos > sv);
                 /*          sz = (size_t)(pos - sv);
                  * First search up to the point where we must close.  
                  * If one doesn't exist, then we can open a new scope.  
                  */  
   
                 for (n = tree->last; n; n = n->parent) {          if (1 == sz && '.' == (*bufp)[sv])
                         assert(0 == tokens[n->tok].ctx ||                  return(ROFF_IGN);
                                         n->tok == tokens[n->tok].ctx);  
                         if (n->tok == tok)  
                                 break;  
                         if (ROFF_SHALLOW & tokens[tok].flags) {  
                                 n = NULL;  
                                 break;  
                         }  
                 }  
   
                 /*          r->last->end = mandoc_malloc(sz + 1);
                  * Create a new scope, as no previous one exists to  
                  * close out.  
                  */  
   
                 if (NULL == n)          memcpy(r->last->end, *bufp + sv, sz);
                         return((*tokens[tok].cb)(tok, tree, argvp, ROFF_ENTER));          r->last->end[(int)sz] = '\0';
   
                 /*          if ((*bufp)[pos])
                  * Close out all intermediary scoped blocks, then hang                  mandoc_msg(MANDOCERR_ARGSLOST, r->parse, ln, pos, NULL);
                  * the current scope from our predecessor's parent.  
                  */  
   
                 do {          return(ROFF_IGN);
                         t = tree->last->tok;  }
                         if ( ! (*tokens[t].cb)(t, tree, NULL, ROFF_EXIT))  
                                 return(0);  
                 } while (t != tok);  
   
                 return((*tokens[tok].cb)(tok, tree, argvp, ROFF_ENTER));  
   /* ARGSUSED */
   static enum rofferr
   roff_block_sub(ROFF_ARGS)
   {
           enum rofft      t;
           int             i, j;
   
           /*
            * First check whether a custom macro exists at this level.  If
            * it does, then check against it.  This is some of groff's
            * stranger behaviours.  If we encountered a custom end-scope
            * tag and that tag also happens to be a "real" macro, then we
            * need to try interpreting it again as a real macro.  If it's
            * not, then return ignore.  Else continue.
            */
   
           if (r->last->end) {
                   for (i = pos, j = 0; r->last->end[j]; j++, i++)
                           if ((*bufp)[i] != r->last->end[j])
                                   break;
   
                   if ('\0' == r->last->end[j] &&
                                   ('\0' == (*bufp)[i] ||
                                    ' ' == (*bufp)[i] ||
                                    '\t' == (*bufp)[i])) {
                           roffnode_pop(r);
                           roffnode_cleanscope(r);
   
                           while (' ' == (*bufp)[i] || '\t' == (*bufp)[i])
                                   i++;
   
                           pos = i;
                           if (ROFF_MAX != roff_parse(r, *bufp, &pos))
                                   return(ROFF_RERUN);
                           return(ROFF_IGN);
                   }
         }          }
   
         /*          /*
          * Now consider explicit-end tags, where we want to close back           * If we have no custom end-query or lookup failed, then try
          * to a specific tag.  Example:           * pulling it out of the hashtable.
          *      .Bl  
          *      .It Item.  
          *      .El  
          * In this, the `El' tag closes out the scope of `Bl'.  
          */           */
   
         assert(tree->last);          t = roff_parse(r, *bufp, &pos);
         assert(tok != tokens[tok].ctx && 0 != tokens[tok].ctx);  
   
         /* LINTED */          /*
         do {           * Macros other than block-end are only significant
                 t = tree->last->tok;           * in `de' blocks; elsewhere, simply throw them away.
                 if ( ! (*tokens[t].cb)(t, tree, NULL, ROFF_EXIT))           */
                         return(0);          if (ROFF_cblock != t) {
         } while (t != tokens[tok].ctx);                  if (ROFF_de == tok)
                           roff_setstr(r, r->last->name, *bufp + ppos, 2);
                   return(ROFF_IGN);
           }
   
         assert(tree->last);          assert(roffs[t].proc);
         return(1);          return((*roffs[t].proc)(r, t, bufp, szp,
                                   ln, ppos, pos, offs));
 }  }
   
   
 static int  /* ARGSUSED */
 rofffindarg(const char *name)  static enum rofferr
   roff_block_text(ROFF_ARGS)
 {  {
         size_t           i;  
   
         /* FIXME: use a table, this is slow but ok for now. */          if (ROFF_de == tok)
                   roff_setstr(r, r->last->name, *bufp + pos, 2);
   
         /* LINTED */          return(ROFF_IGN);
         for (i = 0; i < ROFF_ARGMAX; i++)  
                 /* LINTED */  
                 if (0 == strcmp(name, tokargnames[i]))  
                         return((int)i);  
   
         return(ROFF_ARGMAX);  
 }  }
   
   
 static int  /* ARGSUSED */
 rofffindtok(const char *buf)  static enum rofferr
   roff_cond_sub(ROFF_ARGS)
 {  {
         char             token[4];          enum rofft       t;
         int              i;          char            *ep;
           int              rr;
   
         for (i = 0; *buf && ! isspace(*buf) && i < 3; i++, buf++)          rr = r->last->rule;
                 token[i] = *buf;          roffnode_cleanscope(r);
           t = roff_parse(r, *bufp, &pos);
   
         if (i == 3)          /*
                 return(ROFF_MAX);           * Fully handle known macros when they are structurally
            * required or when the conditional evaluated to true.
            */
   
         token[i] = 0;          if ((ROFF_MAX != t) &&
               (rr || ROFFMAC_STRUCT & roffs[t].flags)) {
                   assert(roffs[t].proc);
                   return((*roffs[t].proc)(r, t, bufp, szp,
                                           ln, ppos, pos, offs));
           }
   
         /* FIXME: use a table, this is slow but ok for now. */          /*
            * If `\}' occurs on a macro line without a preceding macro,
            * drop the line completely.
            */
   
         /* LINTED */          ep = *bufp + pos;
         for (i = 0; i < ROFF_MAX; i++)          if ('\\' == ep[0] && '}' == ep[1])
                 /* LINTED */                  rr = 0;
                 if (0 == strcmp(toknames[i], token))  
                         return((int)i);  
   
         return(ROFF_MAX);          /* Always check for the closing delimiter `\}'. */
   
           while (NULL != (ep = strchr(ep, '\\'))) {
                   if ('}' == *(++ep)) {
                           *ep = '&';
                           roff_ccond(r, ln, ep - *bufp - 1);
                   }
                   ++ep;
           }
           return(rr ? ROFF_CONT : ROFF_IGN);
 }  }
   
   /* ARGSUSED */
   static enum rofferr
   roff_cond_text(ROFF_ARGS)
   {
           char            *ep;
           int              rr;
   
           rr = r->last->rule;
           roffnode_cleanscope(r);
   
           ep = *bufp + pos;
           while (NULL != (ep = strchr(ep, '\\'))) {
                   if ('}' == *(++ep)) {
                           *ep = '&';
                           roff_ccond(r, ln, ep - *bufp - 1);
                   }
                   ++ep;
           }
           return(rr ? ROFF_CONT : ROFF_IGN);
   }
   
   /*
    * Parse a single signed integer number.  Stop at the first non-digit.
    * If there is at least one digit, return success and advance the
    * parse point, else return failure and let the parse point unchanged.
    * Ignore overflows, treat them just like the C language.
    */
 static int  static int
 roffispunct(const char *p)  roff_getnum(const char *v, int *pos, int *res)
 {  {
           int      myres, n, p;
   
         if (0 == *p)          if (NULL == res)
                 return(0);                  res = &myres;
         if (0 != *(p + 1))  
                 return(0);  
   
         switch (*p) {          p = *pos;
         case('{'):          n = v[p] == '-';
           if (n)
                   p++;
   
           for (*res = 0; isdigit((unsigned char)v[p]); p++)
                   *res = 10 * *res + v[p] - '0';
           if (p == *pos + n)
                   return 0;
   
           if (n)
                   *res = -*res;
   
           *pos = p;
           return 1;
   }
   
   /*
    * Evaluate a string comparison condition.
    * The first character is the delimiter.
    * Succeed if the string up to its second occurrence
    * matches the string up to its third occurence.
    * Advance the cursor after the third occurrence
    * or lacking that, to the end of the line.
    */
   static int
   roff_evalstrcond(const char *v, int *pos)
   {
           const char      *s1, *s2, *s3;
           int              match;
   
           match = 0;
           s1 = v + *pos;          /* initial delimiter */
           s2 = s1 + 1;            /* for scanning the first string */
           s3 = strchr(s2, *s1);   /* for scanning the second string */
   
           if (NULL == s3)         /* found no middle delimiter */
                   goto out;
   
           while ('\0' != *++s3) {
                   if (*s2 != *s3) {  /* mismatch */
                           s3 = strchr(s3, *s1);
                           break;
                   }
                   if (*s3 == *s1) {  /* found the final delimiter */
                           match = 1;
                           break;
                   }
                   s2++;
           }
   
   out:
           if (NULL == s3)
                   s3 = strchr(s2, '\0');
           else
                   s3++;
           *pos = s3 - v;
           return(match);
   }
   
   /*
    * Evaluate an optionally negated single character, numerical,
    * or string condition.
    */
   static int
   roff_evalcond(const char *v, int *pos)
   {
           int      wanttrue, number;
   
           if ('!' == v[*pos]) {
                   wanttrue = 0;
                   (*pos)++;
           } else
                   wanttrue = 1;
   
           switch (v[*pos]) {
           case ('n'):
                 /* FALLTHROUGH */                  /* FALLTHROUGH */
         case('.'):          case ('o'):
                   (*pos)++;
                   return(wanttrue);
           case ('c'):
                 /* FALLTHROUGH */                  /* FALLTHROUGH */
         case(','):          case ('d'):
                 /* FALLTHROUGH */                  /* FALLTHROUGH */
         case(';'):          case ('e'):
                 /* FALLTHROUGH */                  /* FALLTHROUGH */
         case(':'):          case ('r'):
                 /* FALLTHROUGH */                  /* FALLTHROUGH */
         case('?'):          case ('t'):
                 /* FALLTHROUGH */                  (*pos)++;
         case('!'):                  return(!wanttrue);
                 /* FALLTHROUGH */  
         case('('):  
                 /* FALLTHROUGH */  
         case(')'):  
                 /* FALLTHROUGH */  
         case('['):  
                 /* FALLTHROUGH */  
         case(']'):  
                 /* FALLTHROUGH */  
         case('}'):  
                 return(1);  
         default:          default:
                 break;                  break;
         }          }
   
         return(0);          if (roff_evalnum(v, pos, &number, 0))
                   return((number > 0) == wanttrue);
           else
                   return(roff_evalstrcond(v, pos) == wanttrue);
 }  }
   
   /* ARGSUSED */
   static enum rofferr
   roff_line_ignore(ROFF_ARGS)
   {
   
 static int          return(ROFF_IGN);
 rofffindcallable(const char *name)  }
   
   /* ARGSUSED */
   static enum rofferr
   roff_cond(ROFF_ARGS)
 {  {
         int              c;  
   
         if (ROFF_MAX == (c = rofffindtok(name)))          roffnode_push(r, tok, NULL, ln, ppos);
                 return(ROFF_MAX);  
         assert(c >= 0 && c < ROFF_MAX);          /*
         return(ROFF_CALLABLE & tokens[c].flags ? c : ROFF_MAX);           * An `.el' has no conditional body: it will consume the value
            * of the current rstack entry set in prior `ie' calls or
            * defaults to DENY.
            *
            * If we're not an `el', however, then evaluate the conditional.
            */
   
           r->last->rule = ROFF_el == tok ?
                   (r->rstackpos < 0 ? 0 : r->rstack[r->rstackpos--]) :
                   roff_evalcond(*bufp, &pos);
   
           /*
            * An if-else will put the NEGATION of the current evaluated
            * conditional into the stack of rules.
            */
   
           if (ROFF_ie == tok) {
                   if (r->rstackpos == RSTACK_MAX - 1) {
                           mandoc_msg(MANDOCERR_MEM,
                                   r->parse, ln, ppos, NULL);
                           return(ROFF_ERR);
                   }
                   r->rstack[++r->rstackpos] = !r->last->rule;
           }
   
           /* If the parent has false as its rule, then so do we. */
   
           if (r->last->parent && !r->last->parent->rule)
                   r->last->rule = 0;
   
           /*
            * Determine scope.
            * If there is nothing on the line after the conditional,
            * not even whitespace, use next-line scope.
            */
   
           if ('\0' == (*bufp)[pos]) {
                   r->last->endspan = 2;
                   goto out;
           }
   
           while (' ' == (*bufp)[pos])
                   pos++;
   
           /* An opening brace requests multiline scope. */
   
           if ('\\' == (*bufp)[pos] && '{' == (*bufp)[pos + 1]) {
                   r->last->endspan = -1;
                   pos += 2;
                   goto out;
           }
   
           /*
            * Anything else following the conditional causes
            * single-line scope.  Warn if the scope contains
            * nothing but trailing whitespace.
            */
   
           if ('\0' == (*bufp)[pos])
                   mandoc_msg(MANDOCERR_NOARGS, r->parse, ln, ppos, NULL);
   
           r->last->endspan = 1;
   
   out:
           *offs = pos;
           return(ROFF_RERUN);
 }  }
   
   
 static struct roffnode *  /* ARGSUSED */
 roffnode_new(int tokid, struct rofftree *tree)  static enum rofferr
   roff_ds(ROFF_ARGS)
 {  {
         struct roffnode *p;          char            *name, *string;
   
         if (NULL == (p = malloc(sizeof(struct roffnode))))  
                 err(1, "malloc");  
   
         p->tok = tokid;          /*
         p->parent = tree->last;           * A symbol is named by the first word following the macro
         tree->last = p;           * invocation up to a space.  Its value is anything after the
            * name's trailing whitespace and optional double-quote.  Thus,
            *
            *  [.ds foo "bar  "     ]
            *
            * will have `bar  "     ' as its value.
            */
   
         return(p);          string = *bufp + pos;
 }          name = roff_getname(r, &string, ln, pos);
           if ('\0' == *name)
                   return(ROFF_IGN);
   
           /* Read past initial double-quote. */
           if ('"' == *string)
                   string++;
   
           /* The rest is the value. */
           roff_setstr(r, name, string, ROFF_as == tok);
           return(ROFF_IGN);
   }
   
   /*
    * Parse a single operator, one or two characters long.
    * If the operator is recognized, return success and advance the
    * parse point, else return failure and let the parse point unchanged.
    */
 static int  static int
 roffargok(int tokid, int argid)  roff_getop(const char *v, int *pos, char *res)
 {  {
         const int       *c;  
   
         if (NULL == (c = tokens[tokid].args))          *res = v[*pos];
   
           switch (*res) {
           case ('+'):
                   /* FALLTHROUGH */
           case ('-'):
                   /* FALLTHROUGH */
           case ('*'):
                   /* FALLTHROUGH */
           case ('/'):
                   /* FALLTHROUGH */
           case ('%'):
                   /* FALLTHROUGH */
           case ('&'):
                   /* FALLTHROUGH */
           case (':'):
                   break;
           case '<':
                   switch (v[*pos + 1]) {
                   case ('='):
                           *res = 'l';
                           (*pos)++;
                           break;
                   case ('>'):
                           *res = '!';
                           (*pos)++;
                           break;
                   case ('?'):
                           *res = 'i';
                           (*pos)++;
                           break;
                   default:
                           break;
                   }
                   break;
           case '>':
                   switch (v[*pos + 1]) {
                   case ('='):
                           *res = 'g';
                           (*pos)++;
                           break;
                   case ('?'):
                           *res = 'a';
                           (*pos)++;
                           break;
                   default:
                           break;
                   }
                   break;
           case '=':
                   if ('=' == v[*pos + 1])
                           (*pos)++;
                   break;
           default:
                 return(0);                  return(0);
           }
           (*pos)++;
   
         for ( ; ROFF_ARGMAX != *c; c++)          return(*res);
                 if (argid == *c)  
                         return(1);  
   
         return(0);  
 }  }
   
   /*
 static void   * Evaluate either a parenthesized numeric expression
 roffnode_free(struct rofftree *tree)   * or a single signed integer number.
    */
   static int
   roff_evalpar(const char *v, int *pos, int *res)
 {  {
         struct roffnode *p;  
   
         assert(tree->last);          if ('(' != v[*pos])
                   return(roff_getnum(v, pos, res));
   
         p = tree->last;          (*pos)++;
         tree->last = tree->last->parent;          if ( ! roff_evalnum(v, pos, res, 1))
         free(p);                  return(0);
 }  
   
           /*
            * Omission of the closing parenthesis
            * is an error in validation mode,
            * but ignored in evaluation mode.
            */
   
           if (')' == v[*pos])
                   (*pos)++;
           else if (NULL == res)
                   return(0);
   
           return(1);
   }
   
   /*
    * Evaluate a complete numeric expression.
    * Proceed left to right, there is no concept of precedence.
    */
 static int  static int
 roffnextopt(const struct rofftree *tree, int tok,  roff_evalnum(const char *v, int *pos, int *res, int skipwhite)
                 char ***in, char **val)  
 {  {
         char            *arg, **argv;          int              mypos, operand2;
         int              v;          char             operator;
   
         *val = NULL;          if (NULL == pos) {
         argv = *in;                  mypos = 0;
         assert(argv);                  pos = &mypos;
           }
   
         if (NULL == (arg = *argv))          if (skipwhite)
                 return(-1);                  while (isspace((unsigned char)v[*pos]))
         if ('-' != *arg)                          (*pos)++;
                 return(-1);  
   
         if (ROFF_ARGMAX == (v = rofffindarg(arg + 1))) {          if ( ! roff_evalpar(v, pos, res))
                 roff_warn(tree, arg, "argument-like parameter `%s' to "                  return(0);
                                 "`%s'", &arg[1], toknames[tok]);  
                 return(-1);  
         }  
   
         if ( ! roffargok(tok, v)) {  
                 roff_warn(tree, arg, "invalid argument parameter `%s' to "  
                                 "`%s'", tokargnames[v], toknames[tok]);  
                 return(-1);  
         }  
   
         if ( ! (ROFF_VALUE & tokenargs[v]))  
                 return(v);  
   
         *in = ++argv;          while (1) {
                   if (skipwhite)
                           while (isspace((unsigned char)v[*pos]))
                                   (*pos)++;
   
         if (NULL == *argv) {                  if ( ! roff_getop(v, pos, &operator))
                 roff_err(tree, arg, "empty value of `%s' for `%s'",                          break;
                                 tokargnames[v], toknames[tok]);  
                 return(ROFF_ARGMAX);  
         }  
   
         return(v);                  if (skipwhite)
 }                          while (isspace((unsigned char)v[*pos]))
                                   (*pos)++;
   
                   if ( ! roff_evalpar(v, pos, &operand2))
                           return(0);
   
 /* ARGSUSED */                  if (skipwhite)
 static  int                          while (isspace((unsigned char)v[*pos]))
 roff_Dd(ROFFCALL_ARGS)                                  (*pos)++;
   
                   if (NULL == res)
                           continue;
   
                   switch (operator) {
                   case ('+'):
                           *res += operand2;
                           break;
                   case ('-'):
                           *res -= operand2;
                           break;
                   case ('*'):
                           *res *= operand2;
                           break;
                   case ('/'):
                           *res /= operand2;
                           break;
                   case ('%'):
                           *res %= operand2;
                           break;
                   case ('<'):
                           *res = *res < operand2;
                           break;
                   case ('>'):
                           *res = *res > operand2;
                           break;
                   case ('l'):
                           *res = *res <= operand2;
                           break;
                   case ('g'):
                           *res = *res >= operand2;
                           break;
                   case ('='):
                           *res = *res == operand2;
                           break;
                   case ('!'):
                           *res = *res != operand2;
                           break;
                   case ('&'):
                           *res = *res && operand2;
                           break;
                   case (':'):
                           *res = *res || operand2;
                           break;
                   case ('i'):
                           if (operand2 < *res)
                                   *res = operand2;
                           break;
                   case ('a'):
                           if (operand2 > *res)
                                   *res = operand2;
                           break;
                   default:
                           abort();
                   }
           }
           return(1);
   }
   
   void
   roff_setreg(struct roff *r, const char *name, int val, char sign)
 {  {
           struct roffreg  *reg;
   
         if (ROFF_BODY & tree->state) {          /* Search for an existing register with the same name. */
                 assert( ! (ROFF_PRELUDE & tree->state));          reg = r->regtab;
                 assert(ROFF_PRELUDE_Dd & tree->state);  
                 return(roff_text(tok, tree, argv, type));          while (reg && strcmp(name, reg->key.p))
                   reg = reg->next;
   
           if (NULL == reg) {
                   /* Create a new register. */
                   reg = mandoc_malloc(sizeof(struct roffreg));
                   reg->key.p = mandoc_strdup(name);
                   reg->key.sz = strlen(name);
                   reg->val = 0;
                   reg->next = r->regtab;
                   r->regtab = reg;
         }          }
   
         assert(ROFF_PRELUDE & tree->state);          if ('+' == sign)
         assert( ! (ROFF_BODY & tree->state));                  reg->val += val;
           else if ('-' == sign)
                   reg->val -= val;
           else
                   reg->val = val;
   }
   
         if (ROFF_PRELUDE_Dd & tree->state) {  /*
                 roff_err(tree, *argv, "repeated `Dd' in prelude");   * Handle some predefined read-only number registers.
    * For now, return -1 if the requested register is not predefined;
    * in case a predefined read-only register having the value -1
    * were to turn up, another special value would have to be chosen.
    */
   static int
   roff_getregro(const char *name)
   {
   
           switch (*name) {
           case ('A'):  /* ASCII approximation mode is always off. */
                 return(0);                  return(0);
         } else if (ROFF_PRELUDE_Dt & tree->state) {          case ('g'):  /* Groff compatibility mode is always on. */
                 roff_err(tree, *argv, "out-of-order `Dd' in prelude");                  return(1);
           case ('H'):  /* Fixed horizontal resolution. */
                   return (24);
           case ('j'):  /* Always adjust left margin only. */
                 return(0);                  return(0);
           case ('T'):  /* Some output device is always defined. */
                   return(1);
           case ('V'):  /* Fixed vertical resolution. */
                   return (40);
           default:
                   return (-1);
         }          }
   }
   
         /* TODO: parse date. */  int
   roff_getreg(const struct roff *r, const char *name)
   {
           struct roffreg  *reg;
           int              val;
   
         assert(NULL == tree->last);          if ('.' == name[0] && '\0' != name[1] && '\0' == name[2]) {
         tree->state |= ROFF_PRELUDE_Dd;                  val = roff_getregro(name + 1);
                   if (-1 != val)
                           return (val);
           }
   
         return(1);          for (reg = r->regtab; reg; reg = reg->next)
                   if (0 == strcmp(name, reg->key.p))
                           return(reg->val);
   
           return(0);
 }  }
   
   static int
 /* ARGSUSED */  roff_getregn(const struct roff *r, const char *name, size_t len)
 static  int  
 roff_Dt(ROFFCALL_ARGS)  
 {  {
           struct roffreg  *reg;
           int              val;
   
         if (ROFF_BODY & tree->state) {          if ('.' == name[0] && 2 == len) {
                 assert( ! (ROFF_PRELUDE & tree->state));                  val = roff_getregro(name + 1);
                 assert(ROFF_PRELUDE_Dt & tree->state);                  if (-1 != val)
                 return(roff_text(tok, tree, argv, type));                          return (val);
         }          }
   
         assert(ROFF_PRELUDE & tree->state);          for (reg = r->regtab; reg; reg = reg->next)
         assert( ! (ROFF_BODY & tree->state));                  if (len == reg->key.sz &&
                       0 == strncmp(name, reg->key.p, len))
                           return(reg->val);
   
         if ( ! (ROFF_PRELUDE_Dd & tree->state)) {          return(0);
                 roff_err(tree, *argv, "out-of-order `Dt' in prelude");  }
                 return(0);  
         } else if (ROFF_PRELUDE_Dt & tree->state) {  static void
                 roff_err(tree, *argv, "repeated `Dt' in prelude");  roff_freereg(struct roffreg *reg)
                 return(0);  {
           struct roffreg  *old_reg;
   
           while (NULL != reg) {
                   free(reg->key.p);
                   old_reg = reg;
                   reg = reg->next;
                   free(old_reg);
         }          }
   }
   
         /* TODO: parse date. */  static enum rofferr
   roff_nr(ROFF_ARGS)
   {
           const char      *key;
           char            *val;
           int              iv;
           char             sign;
   
         assert(NULL == tree->last);          val = *bufp + pos;
         tree->state |= ROFF_PRELUDE_Dt;          key = roff_getname(r, &val, ln, pos);
   
         return(1);          sign = *val;
           if ('+' == sign || '-' == sign)
                   val++;
   
           if (roff_evalnum(val, NULL, &iv, 0))
                   roff_setreg(r, key, iv, sign);
   
           return(ROFF_IGN);
 }  }
   
   static enum rofferr
   roff_rr(ROFF_ARGS)
   {
           struct roffreg  *reg, **prev;
           const char      *name;
           char            *cp;
   
           cp = *bufp + pos;
           name = roff_getname(r, &cp, ln, pos);
   
           prev = &r->regtab;
           while (1) {
                   reg = *prev;
                   if (NULL == reg || !strcmp(name, reg->key.p))
                           break;
                   prev = &reg->next;
           }
           if (NULL != reg) {
                   *prev = reg->next;
                   free(reg->key.p);
                   free(reg);
           }
           return(ROFF_IGN);
   }
   
 /* ARGSUSED */  /* ARGSUSED */
 static  int  static enum rofferr
 roff_Os(ROFFCALL_ARGS)  roff_rm(ROFF_ARGS)
 {  {
           const char       *name;
           char             *cp;
   
         if (ROFF_EXIT == type) {          cp = *bufp + pos;
                 roffnode_free(tree);          while ('\0' != *cp) {
                 return((*tree->cb.rofftail)(tree->arg));                  name = roff_getname(r, &cp, ln, (int)(cp - *bufp));
         } else if (ROFF_BODY & tree->state) {                  if ('\0' != *name)
                 assert( ! (ROFF_PRELUDE & tree->state));                          roff_setstr(r, name, NULL, 0);
                 assert(ROFF_PRELUDE_Os & tree->state);  
                 return(roff_text(tok, tree, argv, type));  
         }          }
           return(ROFF_IGN);
   }
   
         assert(ROFF_PRELUDE & tree->state);  /* ARGSUSED */
         if ( ! (ROFF_PRELUDE_Dt & tree->state) ||  static enum rofferr
                         ! (ROFF_PRELUDE_Dd & tree->state)) {  roff_it(ROFF_ARGS)
                 roff_err(tree, *argv, "out-of-order `Os' in prelude");  {
                 return(0);          char            *cp;
           size_t           len;
           int              iv;
   
           /* Parse the number of lines. */
           cp = *bufp + pos;
           len = strcspn(cp, " \t");
           cp[len] = '\0';
           if ((iv = mandoc_strntoi(cp, len, 10)) <= 0) {
                   mandoc_msg(MANDOCERR_NUMERIC, r->parse,
                                   ln, ppos, *bufp + 1);
                   return(ROFF_IGN);
         }          }
           cp += len + 1;
   
         /* TODO: extract OS. */          /* Arm the input line trap. */
           roffit_lines = iv;
           roffit_macro = mandoc_strdup(cp);
           return(ROFF_IGN);
   }
   
         tree->state |= ROFF_PRELUDE_Os;  /* ARGSUSED */
         tree->state &= ~ROFF_PRELUDE;  static enum rofferr
         tree->state |= ROFF_BODY;  roff_Dd(ROFF_ARGS)
   {
           const char *const       *cp;
   
         assert(NULL == tree->last);          if (0 == ((MPARSE_MDOC | MPARSE_QUICK) & r->options))
                   for (cp = __mdoc_reserved; *cp; cp++)
                           roff_setstr(r, *cp, NULL, 0);
   
         if (NULL == roffnode_new(tok, tree))          return(ROFF_CONT);
                 return(0);  
   
         return((*tree->cb.roffhead)(tree->arg));  
 }  }
   
   /* ARGSUSED */
   static enum rofferr
   roff_TH(ROFF_ARGS)
   {
           const char *const       *cp;
   
           if (0 == (MPARSE_QUICK & r->options))
                   for (cp = __man_reserved; *cp; cp++)
                           roff_setstr(r, *cp, NULL, 0);
   
           return(ROFF_CONT);
   }
   
 /* ARGSUSED */  /* ARGSUSED */
 static int  static enum rofferr
 roff_layout(ROFFCALL_ARGS)  roff_TE(ROFF_ARGS)
 {  {
         int              i, c, argcp[ROFF_MAXARG];  
         char            *v, *argvp[ROFF_MAXARG];  
   
         if (ROFF_PRELUDE & tree->state) {          if (NULL == r->tbl)
                 roff_err(tree, *argv, "`%s' disallowed in prelude",                  mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
                                 toknames[tok]);          else
                 return(0);                  tbl_end(&r->tbl);
         }  
   
         if (ROFF_EXIT == type) {          return(ROFF_IGN);
                 roffnode_free(tree);  }
                 return((*tree->cb.roffblkout)(tree->arg, tok));  
         }  
   
         i = 0;  /* ARGSUSED */
         argv++;  static enum rofferr
   roff_T_(ROFF_ARGS)
   {
   
         while (-1 != (c = roffnextopt(tree, tok, &argv, &v))) {          if (NULL == r->tbl)
                 if (ROFF_ARGMAX == c)                  mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
                         return(0);          else
                   tbl_restart(ppos, ln, r->tbl);
   
                 argcp[i] = c;          return(ROFF_IGN);
                 argvp[i] = v;  }
                 i++;  
                 argv++;  
         }  
   
         argcp[i] = ROFF_ARGMAX;  #if 0
         argvp[i] = NULL;  static int
   roff_closeeqn(struct roff *r)
   {
   
         if (NULL == roffnode_new(tok, tree))          return(r->eqn && ROFF_EQN == eqn_end(&r->eqn) ? 1 : 0);
                 return(0);  }
   #endif
   
         if ( ! (*tree->cb.roffblkin)(tree->arg, tok, argcp, argvp))  static void
                 return(0);  roff_openeqn(struct roff *r, const char *name, int line,
                   int offs, const char *buf)
   {
           struct eqn_node *e;
           int              poff;
   
         if (NULL == *argv)          assert(NULL == r->eqn);
                 return(1);          e = eqn_alloc(name, offs, line, r->parse);
   
         if ( ! (*tree->cb.roffin)(tree->arg, tok, argcp, argvp))          if (r->last_eqn)
                 return(0);                  r->last_eqn->next = e;
           else
                   r->first_eqn = r->last_eqn = e;
   
         if ( ! (ROFF_PARSED & tokens[tok].flags)) {          r->eqn = r->last_eqn = e;
                 i = 0;  
                 while (*argv) {          if (buf) {
                         if ( ! (*tree->cb.roffdata)(tree->arg, i, *argv++))                  poff = 0;
                                 return(0);                  eqn_read(&r->eqn, line, buf, offs, &poff);
                         i = 1;  
                 }  
                 return((*tree->cb.roffout)(tree->arg, tok));  
         }          }
   }
   
         i = 0;  /* ARGSUSED */
         while (*argv) {  static enum rofferr
                 if (ROFF_MAX == (c = rofffindcallable(*argv))) {  roff_EQ(ROFF_ARGS)
                         assert(tree->arg);  {
                         if ( ! (*tree->cb.roffdata)  
                                         (tree->arg, i, *argv++))  
                                 return(0);  
                         i = 1;  
                         continue;  
                 }  
   
                 if (NULL == tokens[c].cb) {          roff_openeqn(r, *bufp + pos, ln, ppos, NULL);
                         roff_err(tree, *argv, "unsupported macro `%s'",          return(ROFF_IGN);
                                         toknames[c]);  }
                         return(0);  
                 }  
   
                 if ( ! (*tokens[c].cb)(c, tree, argv, ROFF_ENTER))  /* ARGSUSED */
                         return(0);  static enum rofferr
   roff_EN(ROFF_ARGS)
   {
   
                 break;          mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
           return(ROFF_IGN);
   }
   
   /* ARGSUSED */
   static enum rofferr
   roff_TS(ROFF_ARGS)
   {
           struct tbl_node *tbl;
   
           if (r->tbl) {
                   mandoc_msg(MANDOCERR_SCOPEBROKEN, r->parse, ln, ppos, NULL);
                   tbl_end(&r->tbl);
         }          }
   
         /*          tbl = tbl_alloc(ppos, ln, r->parse);
          * If we're the first parser (*argv == tree->cur) then purge out  
          * any additional punctuation, should there be any remaining at  
          * the end of line.  
          */  
   
         if ( ! (ROFF_PARSED & tokens[tok].flags && *argv))          if (r->last_tbl)
                 return((*tree->cb.roffout)(tree->arg, tok));                  r->last_tbl->next = tbl;
           else
                   r->first_tbl = r->last_tbl = tbl;
   
         i = 0;          r->tbl = r->last_tbl = tbl;
         while (argv[i])          return(ROFF_IGN);
                 i++;  }
   
         assert(i > 0);  /* ARGSUSED */
         if ( ! roffispunct(argv[--i]))  static enum rofferr
                 return((*tree->cb.roffout)(tree->arg, tok));  roff_cc(ROFF_ARGS)
   {
           const char      *p;
   
         while (i >= 0 && roffispunct(argv[i]))          p = *bufp + pos;
                 i--;  
   
         assert(0 != i);          if ('\0' == *p || '.' == (r->control = *p++))
         i++;                  r->control = 0;
   
         /* LINTED */          if ('\0' != *p)
         while (argv[i])                  mandoc_msg(MANDOCERR_ARGCOUNT, r->parse, ln, ppos, NULL);
                 if ( ! (*tree->cb.roffdata)(tree->arg, 0, argv[i++]))  
                         return(0);  
   
         return((*tree->cb.roffout)(tree->arg, tok));          return(ROFF_IGN);
 }  }
   
   
 /* ARGSUSED */  /* ARGSUSED */
 static int  static enum rofferr
 roff_text(ROFFCALL_ARGS)  roff_tr(ROFF_ARGS)
 {  {
         int              i, j, first, c, argcp[ROFF_MAXARG];          const char      *p, *first, *second;
         char            *v, *argvp[ROFF_MAXARG];          size_t           fsz, ssz;
           enum mandoc_esc  esc;
   
         if (ROFF_PRELUDE & tree->state) {          p = *bufp + pos;
                 roff_err(tree, *argv, "`%s' disallowed in prelude",  
                                 toknames[tok]);          if ('\0' == *p) {
                 return(0);                  mandoc_msg(MANDOCERR_ARGCOUNT, r->parse, ln, ppos, NULL);
                   return(ROFF_IGN);
         }          }
   
         /* FIXME: breaks if passed from roff_layout. */          while ('\0' != *p) {
         first = *argv == tree->cur;                  fsz = ssz = 1;
   
         i = 0;                  first = p++;
         argv++;                  if ('\\' == *first) {
                           esc = mandoc_escape(&p, NULL, NULL);
                           if (ESCAPE_ERROR == esc) {
                                   mandoc_msg
                                           (MANDOCERR_BADESCAPE, r->parse,
                                            ln, (int)(p - *bufp), NULL);
                                   return(ROFF_IGN);
                           }
                           fsz = (size_t)(p - first);
                   }
   
         while (-1 != (c = roffnextopt(tree, tok, &argv, &v))) {                  second = p++;
                 if (ROFF_ARGMAX == c)                  if ('\\' == *second) {
                         return(0);                          esc = mandoc_escape(&p, NULL, NULL);
                           if (ESCAPE_ERROR == esc) {
                                   mandoc_msg
                                           (MANDOCERR_BADESCAPE, r->parse,
                                            ln, (int)(p - *bufp), NULL);
                                   return(ROFF_IGN);
                           }
                           ssz = (size_t)(p - second);
                   } else if ('\0' == *second) {
                           mandoc_msg(MANDOCERR_ARGCOUNT, r->parse,
                                           ln, (int)(p - *bufp), NULL);
                           second = " ";
                           p--;
                   }
   
                 argcp[i] = c;                  if (fsz > 1) {
                 argvp[i] = v;                          roff_setstrn(&r->xmbtab, first,
                 i++;                                          fsz, second, ssz, 0);
                 argv++;                          continue;
                   }
   
                   if (NULL == r->xtab)
                           r->xtab = mandoc_calloc
                                   (128, sizeof(struct roffstr));
   
                   free(r->xtab[(int)*first].p);
                   r->xtab[(int)*first].p = mandoc_strndup(second, ssz);
                   r->xtab[(int)*first].sz = ssz;
         }          }
   
         argcp[i] = ROFF_ARGMAX;          return(ROFF_IGN);
         argvp[i] = NULL;  }
   
         if ( ! (*tree->cb.roffin)(tree->arg, tok, argcp, argvp))  /* ARGSUSED */
                 return(0);  static enum rofferr
   roff_so(ROFF_ARGS)
   {
           char *name;
   
         if ( ! (ROFF_PARSED & tokens[tok].flags)) {          mandoc_msg(MANDOCERR_SO, r->parse, ln, ppos, NULL);
                 i = 0;  
                 while (*argv) {          /*
                         if ( ! (*tree->cb.roffdata)(tree->arg, i, *argv++))           * Handle `so'.  Be EXTREMELY careful, as we shouldn't be
                                 return(0);           * opening anything that's not in our cwd or anything beneath
                         i = 1;           * it.  Thus, explicitly disallow traversing up the file-system
                 }           * or using absolute paths.
                 return((*tree->cb.roffout)(tree->arg, tok));           */
   
           name = *bufp + pos;
           if ('/' == *name || strstr(name, "../") || strstr(name, "/..")) {
                   mandoc_msg(MANDOCERR_SOPATH, r->parse, ln, pos, NULL);
                   return(ROFF_ERR);
         }          }
   
         i = 0;          *offs = pos;
         while (*argv) {          return(ROFF_SO);
                 if (ROFF_MAX == (c = rofffindcallable(*argv))) {  }
                         /*  
                          * If all that remains is roff punctuation, then  
                          * close out our scope and return.  
                          */  
                         if (roffispunct(*argv)) {  
                                 for (j = 0; argv[j]; j++)  
                                         if ( ! roffispunct(argv[j]))  
                                                 break;  
                                 if (NULL == argv[j])  
                                         break;  
                                 i = 1;  
                         }  
   
                         if ( ! (*tree->cb.roffdata)  
                                         (tree->arg, i, *argv++))  
                                 return(0);  
   
                         i = 1;  /* ARGSUSED */
   static enum rofferr
   roff_userdef(ROFF_ARGS)
   {
           const char       *arg[9];
           char             *cp, *n1, *n2;
           int               i;
   
           /*
            * Collect pointers to macro argument strings
            * and NUL-terminate them.
            */
           cp = *bufp + pos;
           for (i = 0; i < 9; i++)
                   arg[i] = '\0' == *cp ? "" :
                       mandoc_getarg(r->parse, &cp, ln, &pos);
   
           /*
            * Expand macro arguments.
            */
           *szp = 0;
           n1 = cp = mandoc_strdup(r->current_string);
           while (NULL != (cp = strstr(cp, "\\$"))) {
                   i = cp[2] - '1';
                   if (0 > i || 8 < i) {
                           /* Not an argument invocation. */
                           cp += 2;
                         continue;                          continue;
                 }                  }
   
                 /*                  *szp = strlen(n1) - 3 + strlen(arg[i]) + 1;
                  * A sub-command has been found.  Execute it and                  n2 = mandoc_malloc(*szp);
                  * discontinue parsing for arguments.  
                  */  
   
                 if (NULL == tokens[c].cb) {                  strlcpy(n2, n1, (size_t)(cp - n1 + 1));
                         roff_err(tree, *argv, "unsupported macro `%s'",                  strlcat(n2, arg[i], *szp);
                                         toknames[c]);                  strlcat(n2, cp + 3, *szp);
                         return(0);  
                 }  
   
                 if ( ! (*tokens[c].cb)(c, tree, argv, ROFF_ENTER))  
                         return(0);  
   
                 break;                  cp = n2 + (cp - n1);
                   free(n1);
                   n1 = n2;
         }          }
   
         if ( ! (*tree->cb.roffout)(tree->arg, tok))          /*
                 return(0);           * Replace the macro invocation
            * by the expanded macro.
         /*  
          * If we're the first parser (*argv == tree->cur) then purge out  
          * any additional punctuation, should there be any remaining at  
          * the end of line.  
          */           */
           free(*bufp);
           *bufp = n1;
           if (0 == *szp)
                   *szp = strlen(*bufp) + 1;
   
         if ( ! (first && *argv))          return(*szp > 1 && '\n' == (*bufp)[(int)*szp - 2] ?
                 return(1);             ROFF_REPARSE : ROFF_APPEND);
   }
   
         i = 0;  static char *
         while (argv[i])  roff_getname(struct roff *r, char **cpp, int ln, int pos)
                 i++;  {
           char     *name, *cp;
   
         assert(i > 0);          name = *cpp;
         if ( ! roffispunct(argv[--i]))          if ('\0' == *name)
                 return(1);                  return(name);
   
         while (i >= 0 && roffispunct(argv[i]))          /* Read until end of name. */
                 i--;          for (cp = name; '\0' != *cp && ' ' != *cp; cp++) {
                   if ('\\' != *cp)
                           continue;
                   cp++;
                   if ('\\' == *cp)
                           continue;
                   mandoc_msg(MANDOCERR_NAMESC, r->parse, ln, pos, NULL);
                   *cp = '\0';
                   name = cp;
           }
   
         assert(0 != i);          /* Nil-terminate name. */
         i++;          if ('\0' != *cp)
                   *(cp++) = '\0';
   
         /* LINTED */          /* Read past spaces. */
         while (argv[i])          while (' ' == *cp)
                 if ( ! (*tree->cb.roffdata)(tree->arg, 0, argv[i++]))                  cp++;
                         return(0);  
   
         return(1);          *cpp = cp;
           return(name);
 }  }
   
   /*
    * Store *string into the user-defined string called *name.
    * To clear an existing entry, call with (*r, *name, NULL, 0).
    * append == 0: replace mode
    * append == 1: single-line append mode
    * append == 2: multiline append mode, append '\n' after each call
    */
   static void
   roff_setstr(struct roff *r, const char *name, const char *string,
           int append)
   {
   
 /* ARGSUSED */          roff_setstrn(&r->strtab, name, strlen(name), string,
 static int                          string ? strlen(string) : 0, append);
 roff_comment(ROFFCALL_ARGS)  }
   
   static void
   roff_setstrn(struct roffkv **r, const char *name, size_t namesz,
                   const char *string, size_t stringsz, int append)
 {  {
           struct roffkv   *n;
           char            *c;
           int              i;
           size_t           oldch, newch;
   
         return(1);          /* Search for an existing string with the same name. */
           n = *r;
   
           while (n && strcmp(name, n->key.p))
                   n = n->next;
   
           if (NULL == n) {
                   /* Create a new string table entry. */
                   n = mandoc_malloc(sizeof(struct roffkv));
                   n->key.p = mandoc_strndup(name, namesz);
                   n->key.sz = namesz;
                   n->val.p = NULL;
                   n->val.sz = 0;
                   n->next = *r;
                   *r = n;
           } else if (0 == append) {
                   free(n->val.p);
                   n->val.p = NULL;
                   n->val.sz = 0;
           }
   
           if (NULL == string)
                   return;
   
           /*
            * One additional byte for the '\n' in multiline mode,
            * and one for the terminating '\0'.
            */
           newch = stringsz + (1 < append ? 2u : 1u);
   
           if (NULL == n->val.p) {
                   n->val.p = mandoc_malloc(newch);
                   *n->val.p = '\0';
                   oldch = 0;
           } else {
                   oldch = n->val.sz;
                   n->val.p = mandoc_realloc(n->val.p, oldch + newch);
           }
   
           /* Skip existing content in the destination buffer. */
           c = n->val.p + (int)oldch;
   
           /* Append new content to the destination buffer. */
           i = 0;
           while (i < (int)stringsz) {
                   /*
                    * Rudimentary roff copy mode:
                    * Handle escaped backslashes.
                    */
                   if ('\\' == string[i] && '\\' == string[i + 1])
                           i++;
                   *c++ = string[i++];
           }
   
           /* Append terminating bytes. */
           if (1 < append)
                   *c++ = '\n';
   
           *c = '\0';
           n->val.sz = (int)(c - n->val.p);
 }  }
   
   static const char *
   roff_getstrn(const struct roff *r, const char *name, size_t len)
   {
           const struct roffkv *n;
           int i;
   
 /* ARGSUSED */          for (n = r->strtab; n; n = n->next)
 static int                  if (0 == strncmp(name, n->key.p, len) &&
 roff_close(ROFFCALL_ARGS)                                  '\0' == n->key.p[(int)len])
                           return(n->val.p);
   
           for (i = 0; i < PREDEFS_MAX; i++)
                   if (0 == strncmp(name, predefs[i].name, len) &&
                                   '\0' == predefs[i].name[(int)len])
                           return(predefs[i].str);
   
           return(NULL);
   }
   
   static void
   roff_freestr(struct roffkv *r)
 {  {
           struct roffkv    *n, *nn;
   
         return(1);          for (n = r; n; n = nn) {
                   free(n->key.p);
                   free(n->val.p);
                   nn = n->next;
                   free(n);
           }
 }  }
   
   const struct tbl_span *
   roff_span(const struct roff *r)
   {
   
           return(r->tbl ? tbl_span(r->tbl) : NULL);
   }
   
 #if notyet  const struct eqn *
 /* ARGSUSED */  roff_eqn(const struct roff *r)
 static int  
 roff_Ns(ROFFCALL_ARGS)  
 {  {
         int              c;  
           return(r->last_eqn ? &r->last_eqn->eqn : NULL);
   }
   
         argv++;  /*
    * Duplicate an input string, making the appropriate character
    * conversations (as stipulated by `tr') along the way.
    * Returns a heap-allocated string with all the replacements made.
    */
   char *
   roff_strdup(const struct roff *r, const char *p)
   {
           const struct roffkv *cp;
           char            *res;
           const char      *pp;
           size_t           ssz, sz;
           enum mandoc_esc  esc;
   
         if (ROFF_MAX != (c = rofffindcallable(*argv))) {          if (NULL == r->xmbtab && NULL == r->xtab)
                 if (NULL == tokens[c].cb) {                  return(mandoc_strdup(p));
                         roff_err(tree, *argv, "unsupported macro `%s'",          else if ('\0' == *p)
                                         toknames[c]);                  return(mandoc_strdup(""));
                         return(0);  
           /*
            * Step through each character looking for term matches
            * (remember that a `tr' can be invoked with an escape, which is
            * a glyph but the escape is multi-character).
            * We only do this if the character hash has been initialised
            * and the string is >0 length.
            */
   
           res = NULL;
           ssz = 0;
   
           while ('\0' != *p) {
                   if ('\\' != *p && r->xtab && r->xtab[(int)*p].p) {
                           sz = r->xtab[(int)*p].sz;
                           res = mandoc_realloc(res, ssz + sz + 1);
                           memcpy(res + ssz, r->xtab[(int)*p].p, sz);
                           ssz += sz;
                           p++;
                           continue;
                   } else if ('\\' != *p) {
                           res = mandoc_realloc(res, ssz + 2);
                           res[ssz++] = *p++;
                           continue;
                 }                  }
                 if ( ! (*tree->cb.roffspecial)(tree->arg, tok))  
                         return(0);  
                 if ( ! (*tokens[c].cb)(c, tree, argv, ROFF_ENTER))  
                         return(0);  
   
                 return(1);                  /* Search for term matches. */
         } else if ( ! (*tree->cb.roffdata)(tree->arg, 0, *argv++))                  for (cp = r->xmbtab; cp; cp = cp->next)
                 return(0);                          if (0 == strncmp(p, cp->key.p, cp->key.sz))
                                   break;
         while (*argv) {  
                 if (ROFF_MAX == (c = rofffindcallable(*argv))) {                  if (NULL != cp) {
                         assert(tree->arg);                          /*
                         if ( ! (*tree->cb.roffdata)                           * A match has been found.
                                         (tree->arg, 1, *argv++))                           * Append the match to the array and move
                                 return(0);                           * forward by its keysize.
                            */
                           res = mandoc_realloc
                                   (res, ssz + cp->val.sz + 1);
                           memcpy(res + ssz, cp->val.p, cp->val.sz);
                           ssz += cp->val.sz;
                           p += (int)cp->key.sz;
                         continue;                          continue;
                 }                  }
                 if (NULL == tokens[c].cb) {  
                         roff_err(tree, *argv, "unsupported macro `%s'",  
                                         toknames[c]);  
                         return(0);  
                 }  
                 if ( ! (*tokens[c].cb)(c, tree, argv, ROFF_ENTER))  
                         return(0);  
   
                 break;                  /*
                    * Handle escapes carefully: we need to copy
                    * over just the escape itself, or else we might
                    * do replacements within the escape itself.
                    * Make sure to pass along the bogus string.
                    */
                   pp = p++;
                   esc = mandoc_escape(&p, NULL, NULL);
                   if (ESCAPE_ERROR == esc) {
                           sz = strlen(pp);
                           res = mandoc_realloc(res, ssz + sz + 1);
                           memcpy(res + ssz, pp, sz);
                           break;
                   }
                   /*
                    * We bail out on bad escapes.
                    * No need to warn: we already did so when
                    * roff_res() was called.
                    */
                   sz = (int)(p - pp);
                   res = mandoc_realloc(res, ssz + sz + 1);
                   memcpy(res + ssz, pp, sz);
                   ssz += sz;
         }          }
   
         return(1);          res[(int)ssz] = '\0';
           return(res);
 }  }
 #endif  
   
   /*
 static void   * Find out whether a line is a macro line or not.
 roff_warn(const struct rofftree *tree, const char *pos, char *fmt, ...)   * If it is, adjust the current position and return one; if it isn't,
    * return zero and don't change the current position.
    * If the control character has been set with `.cc', then let that grain
    * precedence.
    * This is slighly contrary to groff, where using the non-breaking
    * control character when `cc' has been invoked will cause the
    * non-breaking macro contents to be printed verbatim.
    */
   int
   roff_getcontrol(const struct roff *r, const char *cp, int *ppos)
 {  {
         va_list          ap;          int             pos;
         char             buf[128];  
   
         va_start(ap, fmt);          pos = *ppos;
         (void)vsnprintf(buf, sizeof(buf), fmt, ap);  
         va_end(ap);  
   
         (*tree->cb.roffmsg)(tree->arg,          if (0 != r->control && cp[pos] == r->control)
                         ROFF_WARN, tree->cur, pos, buf);                  pos++;
 }          else if (0 != r->control)
                   return(0);
           else if ('\\' == cp[pos] && '.' == cp[pos + 1])
                   pos += 2;
           else if ('.' == cp[pos] || '\'' == cp[pos])
                   pos++;
           else
                   return(0);
   
           while (' ' == cp[pos] || '\t' == cp[pos])
                   pos++;
   
 static void          *ppos = pos;
 roff_err(const struct rofftree *tree, const char *pos, char *fmt, ...)          return(1);
 {  
         va_list          ap;  
         char             buf[128];  
   
         va_start(ap, fmt);  
         (void)vsnprintf(buf, sizeof(buf), fmt, ap);  
         va_end(ap);  
   
         (*tree->cb.roffmsg)(tree->arg,  
                         ROFF_ERROR, tree->cur, pos, buf);  
 }  }

Legend:
Removed from v.1.26  
changed lines
  Added in v.1.206

CVSweb