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

Annotation of mandoc/roff.c, Revision 1.5

1.5     ! kristaps    1: /* $Id: roff.c,v 1.4 2008/11/25 12:14:02 kristaps Exp $ */
1.1       kristaps    2: /*
                      3:  * Copyright (c) 2008 Kristaps Dzonsons <kristaps@kth.se>
                      4:  *
                      5:  * Permission to use, copy, modify, and distribute this software for any
                      6:  * purpose with or without fee is hereby granted, provided that the
                      7:  * above copyright notice and this permission notice appear in all
                      8:  * copies.
                      9:  *
                     10:  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
                     11:  * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
                     12:  * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
                     13:  * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
                     14:  * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
                     15:  * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
                     16:  * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
                     17:  * PERFORMANCE OF THIS SOFTWARE.
                     18:  */
                     19: #include <assert.h>
                     20: #include <ctype.h>
                     21: #include <err.h>
                     22: #include <stdlib.h>
                     23: #include <stdio.h>
                     24: #include <string.h>
                     25: #include <time.h>
                     26:
                     27: #include "libmdocml.h"
                     28: #include "private.h"
                     29:
                     30: #define        ROFF_MAXARG       10
                     31:
1.5     ! kristaps   32: /* Whether we're entering or leaving a roff scope. */
1.1       kristaps   33: enum   roffd {
                     34:        ROFF_ENTER = 0,
                     35:        ROFF_EXIT
                     36: };
                     37:
1.5     ! kristaps   38: /* The type of a macro (see mdoc(7) for more). */
1.1       kristaps   39: enum   rofftype {
                     40:        ROFF_COMMENT,
                     41:        ROFF_TEXT,
                     42:        ROFF_LAYOUT
                     43: };
                     44:
1.5     ! kristaps   45: /* Arguments passed to a macro callback. */
1.1       kristaps   46: #define        ROFFCALL_ARGS \
1.2       kristaps   47:        int tok, struct rofftree *tree, \
                     48:        const char *argv[], enum roffd type
1.1       kristaps   49:
                     50: struct rofftree;
                     51:
1.5     ! kristaps   52: /* Describes a roff token (like D1 or Sh). */
1.1       kristaps   53: struct rofftok {
1.5     ! kristaps   54:        int             (*cb)(ROFFCALL_ARGS);   /* Callback. */
        !            55:        const int        *args;                 /* Args (or NULL). */
        !            56:        enum rofftype     type;                 /* Type of macro. */
        !            57:        int               symm;                 /* FIXME */
1.1       kristaps   58:        int               flags;
1.5     ! kristaps   59: #define        ROFF_NESTED      (1 << 0)               /* Nested-layout. */
        !            60: #define        ROFF_PARSED      (1 << 1)               /* "Parsed". */
        !            61: #define        ROFF_CALLABLE    (1 << 2)               /* "Callable". */
        !            62: #define        ROFF_QUOTES      (1 << 3)               /* Quoted args. */
1.1       kristaps   63: };
                     64:
1.5     ! kristaps   65: /* An argument to a roff token (like -split or -enum). */
1.1       kristaps   66: struct roffarg {
                     67:        int               flags;
1.5     ! kristaps   68: #define        ROFF_VALUE       (1 << 0)               /* Has a value. */
1.1       kristaps   69: };
                     70:
1.5     ! kristaps   71: /* mdocml remembers only the current parse node and the chain leading to
        !            72:  * the document root (scopes).
        !            73:  */
1.1       kristaps   74: struct roffnode {
1.5     ! kristaps   75:        int               tok;                  /* Token id. */
        !            76:        struct roffnode  *parent;               /* Parent (or NULL). */
        !            77:        size_t            line;                 /* Parsed at line. */
1.1       kristaps   78: };
                     79:
1.5     ! kristaps   80: /* State of file parse. */
1.1       kristaps   81: struct rofftree {
1.5     ! kristaps   82:        struct roffnode  *last;                 /* Last parsed node. */
        !            83:        time_t            date;                 /* `Dd' results. */
        !            84:        char              os[64];               /* `Os' results. */
        !            85:        char              title[64];            /* `Dt' results. */
        !            86:        char              section[64];          /* `Dt' results. */
        !            87:        char              volume[64];           /* `Dt' results. */
1.1       kristaps   88:        int               state;
1.5     ! kristaps   89: #define        ROFF_PRELUDE     (1 << 1)               /* In roff prelude. */
        !            90:        /* FIXME: if we had prev ptrs, this wouldn't be necessary. */
        !            91: #define        ROFF_PRELUDE_Os  (1 << 2)               /* `Os' is parsed. */
        !            92: #define        ROFF_PRELUDE_Dt  (1 << 3)               /* `Dt' is parsed. */
        !            93: #define        ROFF_PRELUDE_Dd  (1 << 4)               /* `Dd' is parsed. */
        !            94: #define        ROFF_BODY        (1 << 5)               /* In roff body. */
        !            95:        roffin           roffin;                /* Text-macro cb. */
        !            96:        roffblkin        roffblkin;             /* Block-macro cb. */
        !            97:        roffout          roffout;               /* Text-macro cb. */
        !            98:        roffblkout       roffblkout;            /* Block-macro cb. */
        !            99:        struct md_mbuf          *mbuf;          /* Output (or NULL). */
        !           100:        const struct md_args    *args;          /* Global args. */
        !           101:        const struct md_rbuf    *rbuf;          /* Input. */
1.1       kristaps  102: };
                    103:
                    104: static int               roff_Dd(ROFFCALL_ARGS);
                    105: static int               roff_Dt(ROFFCALL_ARGS);
                    106: static int               roff_Os(ROFFCALL_ARGS);
1.2       kristaps  107: static int               roff_layout(ROFFCALL_ARGS);
                    108: static int               roff_text(ROFFCALL_ARGS);
1.1       kristaps  109:
1.4       kristaps  110: static struct roffnode  *roffnode_new(int, struct rofftree *);
1.1       kristaps  111: static void              roffnode_free(int, struct rofftree *);
                    112:
                    113: static int               rofffindtok(const char *);
                    114: static int               rofffindarg(const char *);
1.2       kristaps  115: static int               rofffindcallable(const char *);
1.1       kristaps  116: static int               roffargs(int, char *, char **);
1.5     ! kristaps  117: static int               roffargok(int, int);
        !           118: static int               roffnextopt(int, const char ***, char **);
1.1       kristaps  119: static int               roffparse(struct rofftree *, char *, size_t);
                    120: static int               textparse(const struct rofftree *,
                    121:                                const char *, size_t);
                    122:
1.5     ! kristaps  123: /* Arguments for `An' macro. */
        !           124: static const int roffarg_An[] = {
        !           125:        ROFF_Split, ROFF_Nosplit, ROFF_ARGMAX };
        !           126: /* Arguments for `Bd' macro. */
        !           127: static const int roffarg_Bd[] = {
        !           128:        ROFF_Ragged, ROFF_Unfilled, ROFF_Literal, ROFF_File,
        !           129:        ROFF_Offset, ROFF_ARGMAX };
        !           130: /* Arguments for `Bl' macro. */
        !           131: static         const int roffarg_Bl[] = {
        !           132:        ROFF_Bullet, ROFF_Dash, ROFF_Hyphen, ROFF_Item, ROFF_Enum,
        !           133:        ROFF_Tag, ROFF_Diag, ROFF_Hang, ROFF_Ohang, ROFF_Inset,
        !           134:        ROFF_Column, ROFF_Offset, ROFF_ARGMAX };
        !           135:
        !           136: /* FIXME: a big list of fixes that must occur.
        !           137:  *
        !           138:  * (1) Distinction not between ROFF_TEXT and ROFF_LAYOUT, but instead
        !           139:  *     ROFF_ATOM and ROFF_NODE, which designate line spacing.  If
        !           140:  *     ROFF_ATOM, we need not remember any state.
        !           141:  *
        !           142:  * (2) Have a maybe-NULL list of possible subsequent children for each
        !           143:  *     node.  Bl, e.g., can only have It children (roffparse).
        !           144:  *
        !           145:  * (3) Have a maybe-NULL list of possible parents for each node.  It,
        !           146:  *     e.g., can only have Bl as a parent (roffparse).
        !           147:  *
        !           148:  *     (N.B. If (2) were complete, (3) wouldn't be necessary.)
        !           149:  *
        !           150:  * (4) Scope rules.  If Pp exists, it closes the scope out from the
        !           151:  *     previous Pp (if it exists).  Same with Sh and Ss.  If El exists,
        !           152:  *     it closes out Bl and interim It.
        !           153:  *
        !           154:  * (5) Nesting.  Sh cannot be any descendant of Sh.  Bl, however, can be
        !           155:  *     nested within an It.
        !           156:  *
        !           157:  * Once that's done, we're golden.
        !           158:  */
1.1       kristaps  159:
1.5     ! kristaps  160: /* Table of all known tokens. */
1.4       kristaps  161: static const struct rofftok tokens[ROFF_MAX] = {
1.5     ! kristaps  162:        {        NULL,       NULL, 0, ROFF_COMMENT, 0 },        /* \" */
        !           163:        {     roff_Dd,       NULL, 0, ROFF_TEXT, 0 },   /* Dd */
        !           164:        {     roff_Dt,       NULL, 0, ROFF_TEXT, 0 },   /* Dt */
        !           165:        {     roff_Os,       NULL, 0, ROFF_TEXT, 0 },   /* Os */
        !           166:        { roff_layout,       NULL, ROFF_Sh, ROFF_LAYOUT, ROFF_PARSED }, /* Sh */
        !           167:        { roff_layout,       NULL, ROFF_Ss, ROFF_LAYOUT, ROFF_PARSED }, /* Ss */
        !           168:        { roff_layout,       NULL, ROFF_Pp, ROFF_LAYOUT, 0 },   /* Pp */
        !           169:        { roff_layout,       NULL, 0, ROFF_TEXT, 0 },           /* D1 */
        !           170:        { roff_layout,       NULL, 0, ROFF_TEXT, 0 },           /* Dl */
        !           171:        { roff_layout, roffarg_Bd, 0, ROFF_LAYOUT, 0 },         /* Bd */
        !           172:        { roff_layout,       NULL, ROFF_Bd, ROFF_LAYOUT, 0 },   /* Ed */
        !           173:        { roff_layout, roffarg_Bl, 0, ROFF_LAYOUT, 0 },         /* Bl */
        !           174:        { roff_layout,       NULL, ROFF_Bl, ROFF_LAYOUT, 0 },   /* El */
        !           175:        { roff_layout,       NULL, ROFF_It, ROFF_LAYOUT, 0 },   /* It */
        !           176:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Ad */
        !           177:        {   roff_text, roffarg_An, 0, ROFF_TEXT, ROFF_PARSED }, /* An */
        !           178:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Ar */
        !           179:        {   roff_text,       NULL, 0, ROFF_TEXT, 0 },   /* Cd */
        !           180:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Cm */
        !           181:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Dv */
        !           182:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Er */
        !           183:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Ev */
        !           184:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Ex */
        !           185:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Fa */
        !           186:        {   roff_text,       NULL, 0, ROFF_TEXT, 0 },   /* Fd */
        !           187:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Fl */
        !           188:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Fn */
        !           189:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Ft */
        !           190:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Ic */
        !           191:        {   roff_text,       NULL, 0, ROFF_TEXT, 0 },   /* In */
        !           192:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Li */
        !           193:        {   roff_text,       NULL, 0, ROFF_TEXT, 0 },   /* Nd */
        !           194:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Nm */
        !           195:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Op */
        !           196:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Ot */
        !           197:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Pa */
        !           198:        {   roff_text,       NULL, 0, ROFF_TEXT, 0 },   /* Rv */
        !           199:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* St */
        !           200:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Va */
        !           201:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Vt */
        !           202:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Xr */
        !           203:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED }, /* %A */
        !           204:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE}, /* %B */
        !           205:        {   roff_text,       NULL, 0, ROFF_TEXT, 0 },   /* %D */
        !           206:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE}, /* %I */
        !           207:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE}, /* %J */
        !           208:        {   roff_text,       NULL, 0, ROFF_TEXT, 0 },   /* %N */
        !           209:        {   roff_text,       NULL, 0, ROFF_TEXT, 0 },   /* %O */
        !           210:        {   roff_text,       NULL, 0, ROFF_TEXT, 0 },   /* %P */
        !           211:        {   roff_text,       NULL, 0, ROFF_TEXT, 0 },   /* %R */
        !           212:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED }, /* %T */
        !           213:        {   roff_text,       NULL, 0, ROFF_TEXT, 0 },   /* %V */
        !           214:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Ac */
        !           215:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Ao */
        !           216:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Aq */
        !           217:        {   roff_text,       NULL, 0, ROFF_TEXT, 0 },   /* At */
        !           218:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Bc */
        !           219:        {   roff_text,       NULL, 0, ROFF_TEXT, 0 },   /* Bf */
        !           220:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Bo */
        !           221:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Bq */
        !           222:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED }, /* Bsx */
        !           223:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED }, /* Bx */
        !           224:        {   roff_text,       NULL, 0, ROFF_TEXT, 0 },   /* Db */
        !           225:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Dc */
        !           226:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Do */
        !           227:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Dq */
        !           228:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Ec */
        !           229:        {   roff_text,       NULL, 0, ROFF_TEXT, 0 },   /* Ef */
        !           230:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Em */
        !           231:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Eo */
        !           232:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED }, /* Fx */
        !           233:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED }, /* Ms */
        !           234:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* No */
        !           235:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Ns */
        !           236:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED }, /* Nx */
        !           237:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED }, /* Ox */
        !           238:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Pc */
        !           239:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED }, /* Pf */
        !           240:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Po */
        !           241:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Pq */
        !           242:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Qc */
        !           243:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Ql */
        !           244:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Qo */
        !           245:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Qq */
        !           246:        {   roff_text,       NULL, 0, ROFF_TEXT, 0 },   /* Re */
        !           247:        {   roff_text,       NULL, 0, ROFF_TEXT, 0 },   /* Rs */
        !           248:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Sc */
        !           249:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* So */
        !           250:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Sq */
        !           251:        {   roff_text,       NULL, 0, ROFF_TEXT, 0 },   /* Sm */
        !           252:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Sx */
        !           253:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Sy */
        !           254:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Tn */
        !           255:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED }, /* Ux */
        !           256:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Xc */
        !           257:        {   roff_text,       NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Xo */
1.1       kristaps  258: };
                    259:
1.5     ! kristaps  260: /* Table of all known token arguments. */
1.4       kristaps  261: static const struct roffarg tokenargs[ROFF_ARGMAX] = {
1.5     ! kristaps  262:        { 0 },                                          /* split */
        !           263:        { 0 },                                          /* nosplit */
        !           264:        { 0 },                                          /* ragged */
        !           265:        { 0 },                                          /* unfilled */
        !           266:        { 0 },                                          /* literal */
        !           267:        { ROFF_VALUE },                                 /* file */
        !           268:        { ROFF_VALUE },                                 /* offset */
        !           269:        { 0 },                                          /* bullet */
        !           270:        { 0 },                                          /* dash */
        !           271:        { 0 },                                          /* hyphen */
        !           272:        { 0 },                                          /* item */
        !           273:        { 0 },                                          /* enum */
        !           274:        { 0 },                                          /* tag */
        !           275:        { 0 },                                          /* diag */
        !           276:        { 0 },                                          /* hang */
        !           277:        { 0 },                                          /* ohang */
        !           278:        { 0 },                                          /* inset */
        !           279:        { 0 },                                          /* column */
        !           280:        { 0 },                                          /* width */
        !           281:        { 0 },                                          /* compact */
1.1       kristaps  282: };
                    283:
1.5     ! kristaps  284: const  char *const toknamesp[ROFF_MAX] =
        !           285:        {
        !           286:        "\\\"",
        !           287:        "Dd",   /* Title macros. */
        !           288:        "Dt",
        !           289:        "Os",
        !           290:        "Sh",   /* Layout macros */
        !           291:        "Ss",
        !           292:        "Pp",
        !           293:        "D1",
        !           294:        "Dl",
        !           295:        "Bd",
        !           296:        "Ed",
        !           297:        "Bl",
        !           298:        "El",
        !           299:        "It",
        !           300:        "Ad",   /* Text macros. */
        !           301:        "An",
        !           302:        "Ar",
        !           303:        "Cd",
        !           304:        "Cm",
        !           305:        "Dr",
        !           306:        "Er",
        !           307:        "Ev",
        !           308:        "Ex",
        !           309:        "Fa",
        !           310:        "Fd",
        !           311:        "Fl",
        !           312:        "Fn",
        !           313:        "Ft",
        !           314:        "Ex",
        !           315:        "Ic",
        !           316:        "In",
        !           317:        "Li",
        !           318:        "Nd",
        !           319:        "Nm",
        !           320:        "Op",
        !           321:        "Ot",
        !           322:        "Pa",
        !           323:        "Rv",
        !           324:        "St",
        !           325:        "Va",
        !           326:        "Vt",
        !           327:        "Xr",
        !           328:        "\%A",  /* General text macros. */
        !           329:        "\%B",
        !           330:        "\%D",
        !           331:        "\%I",
        !           332:        "\%J",
        !           333:        "\%N",
        !           334:        "\%O",
        !           335:        "\%P",
        !           336:        "\%R",
        !           337:        "\%T",
        !           338:        "\%V",
        !           339:        "Ac",
        !           340:        "Ao",
        !           341:        "Aq",
        !           342:        "At",
        !           343:        "Bc",
        !           344:        "Bf",
        !           345:        "Bo",
        !           346:        "Bq",
        !           347:        "Bsx",
        !           348:        "Bx",
        !           349:        "Db",
        !           350:        "Dc",
        !           351:        "Do",
        !           352:        "Dq",
        !           353:        "Ec",
        !           354:        "Ef",
        !           355:        "Em",
        !           356:        "Eo",
        !           357:        "Fx",
        !           358:        "Ms",
        !           359:        "No",
        !           360:        "Ns",
        !           361:        "Nx",
        !           362:        "Ox",
        !           363:        "Pc",
        !           364:        "Pf",
        !           365:        "Po",
        !           366:        "Pq",
        !           367:        "Qc",
        !           368:        "Ql",
        !           369:        "Qo",
        !           370:        "Qq",
        !           371:        "Re",
        !           372:        "Rs",
        !           373:        "Sc",
        !           374:        "So",
        !           375:        "Sq",
        !           376:        "Sm",
        !           377:        "Sx",
        !           378:        "Sy",
        !           379:        "Tn",
        !           380:        "Ux",
        !           381:        "Xc",   /* FIXME: do not support! */
        !           382:        "Xo",   /* FIXME: do not support! */
        !           383:        };
        !           384:
        !           385: const  char *const tokargnamesp[ROFF_ARGMAX] =
        !           386:        {
        !           387:        "split",
        !           388:        "nosplit",
        !           389:        "ragged",
        !           390:        "unfilled",
        !           391:        "literal",
        !           392:        "file",
        !           393:        "offset",
        !           394:        "bullet",
        !           395:        "dash",
        !           396:        "hyphen",
        !           397:        "item",
        !           398:        "enum",
        !           399:        "tag",
        !           400:        "diag",
        !           401:        "hang",
        !           402:        "ohang",
        !           403:        "inset",
        !           404:        "column",
        !           405:        "width",
        !           406:        "compact",
        !           407:        };
        !           408:
        !           409: const  char *const *toknames = toknamesp;
        !           410: const  char *const *tokargnames = tokargnamesp;
1.4       kristaps  411:
1.1       kristaps  412:
                    413: int
                    414: roff_free(struct rofftree *tree, int flush)
                    415: {
                    416:        int              error;
                    417:
                    418:        assert(tree->mbuf);
                    419:        if ( ! flush)
                    420:                tree->mbuf = NULL;
                    421:
                    422:        /* LINTED */
                    423:        while (tree->last)
                    424:                if ( ! (*tokens[tree->last->tok].cb)
1.2       kristaps  425:                                (tree->last->tok, tree, NULL, ROFF_EXIT))
1.1       kristaps  426:                        /* Disallow flushing. */
                    427:                        tree->mbuf = NULL;
                    428:
                    429:        error = tree->mbuf ? 0 : 1;
                    430:
                    431:        if (tree->mbuf && (ROFF_PRELUDE & tree->state)) {
                    432:                warnx("%s: prelude never finished",
                    433:                                tree->rbuf->name);
                    434:                error = 1;
                    435:        }
                    436:
                    437:        free(tree);
                    438:        return(error ? 0 : 1);
                    439: }
                    440:
                    441:
                    442: struct rofftree *
                    443: roff_alloc(const struct md_args *args, struct md_mbuf *out,
1.4       kristaps  444:                const struct md_rbuf *in, roffin textin,
                    445:                roffout textout, roffblkin blkin, roffblkout blkout)
1.1       kristaps  446: {
                    447:        struct rofftree *tree;
                    448:
                    449:        if (NULL == (tree = calloc(1, sizeof(struct rofftree)))) {
                    450:                warn("malloc");
                    451:                return(NULL);
                    452:        }
                    453:
                    454:        tree->state = ROFF_PRELUDE;
                    455:        tree->args = args;
                    456:        tree->mbuf = out;
                    457:        tree->rbuf = in;
1.4       kristaps  458:        tree->roffin = textin;
                    459:        tree->roffout = textout;
                    460:        tree->roffblkin = blkin;
                    461:        tree->roffblkout = blkout;
1.1       kristaps  462:
                    463:        return(tree);
                    464: }
                    465:
                    466:
                    467: int
                    468: roff_engine(struct rofftree *tree, char *buf, size_t sz)
                    469: {
                    470:
                    471:        if (0 == sz) {
                    472:                warnx("%s: blank line (line %zu)",
                    473:                                tree->rbuf->name,
                    474:                                tree->rbuf->line);
                    475:                return(0);
                    476:        } else if ('.' != *buf)
                    477:                return(textparse(tree, buf, sz));
                    478:
                    479:        return(roffparse(tree, buf, sz));
                    480: }
                    481:
                    482:
                    483: static int
                    484: textparse(const struct rofftree *tree, const char *buf, size_t sz)
                    485: {
                    486:
                    487:        if (NULL == tree->last) {
                    488:                warnx("%s: unexpected text (line %zu)",
                    489:                                tree->rbuf->name,
                    490:                                tree->rbuf->line);
                    491:                return(0);
                    492:        } else if (NULL == tree->last->parent) {
                    493:                warnx("%s: disallowed text (line %zu)",
                    494:                                tree->rbuf->name,
                    495:                                tree->rbuf->line);
                    496:                return(0);
                    497:        }
                    498:
                    499:        /* Print text. */
                    500:
                    501:        return(1);
                    502: }
                    503:
                    504:
                    505: static int
                    506: roffargs(int tok, char *buf, char **argv)
                    507: {
                    508:        int              i;
                    509:
                    510:        (void)tok;/* FIXME: quotable strings? */
                    511:
                    512:        assert(tok >= 0 && tok < ROFF_MAX);
                    513:        assert('.' == *buf);
                    514:
                    515:        /* LINTED */
                    516:        for (i = 0; *buf && i < ROFF_MAXARG; i++) {
                    517:                argv[i] = buf++;
                    518:                while (*buf && ! isspace(*buf))
                    519:                        buf++;
                    520:                if (0 == *buf) {
                    521:                        continue;
                    522:                }
                    523:                *buf++ = 0;
                    524:                while (*buf && isspace(*buf))
                    525:                        buf++;
                    526:        }
                    527:
                    528:        assert(i > 0);
                    529:        if (i < ROFF_MAXARG)
                    530:                argv[i] = NULL;
                    531:
                    532:        return(ROFF_MAXARG > i);
                    533: }
                    534:
                    535:
                    536: static int
                    537: roffparse(struct rofftree *tree, char *buf, size_t sz)
                    538: {
                    539:        int               tok, t;
                    540:        struct roffnode  *node;
                    541:        char             *argv[ROFF_MAXARG];
                    542:        const char      **argvp;
                    543:
                    544:        assert(sz > 0);
                    545:
                    546:        /*
                    547:         * Extract the token identifier from the buffer.  If there's no
                    548:         * callback for the token (comment, etc.) then exit immediately.
                    549:         * We don't do any error handling (yet), so if the token doesn't
                    550:         * exist, die.
                    551:         */
                    552:
                    553:        if (3 > sz) {
                    554:                warnx("%s: malformed line (line %zu)",
                    555:                                tree->rbuf->name,
                    556:                                tree->rbuf->line);
                    557:                return(0);
1.5     ! kristaps  558:
        !           559:        /* FIXME: .Bsx is three letters! */
1.1       kristaps  560:        } else if (ROFF_MAX == (tok = rofffindtok(buf + 1))) {
                    561:                warnx("%s: unknown line token `%c%c' (line %zu)",
                    562:                                tree->rbuf->name,
                    563:                                *(buf + 1), *(buf + 2),
                    564:                                tree->rbuf->line);
                    565:                return(0);
                    566:        } else if (ROFF_COMMENT == tokens[tok].type)
                    567:                /* Ignore comment tokens. */
                    568:                return(1);
                    569:
                    570:        if ( ! roffargs(tok, buf, argv)) {
                    571:                warnx("%s: too many arguments to `%s' (line %zu)",
1.4       kristaps  572:                                tree->rbuf->name, toknames[tok],
1.1       kristaps  573:                                tree->rbuf->line);
                    574:                return(0);
                    575:        }
                    576:
                    577:        /*
                    578:         * If this is a non-nestable layout token and we're below a
                    579:         * token of the same type, then recurse upward to the token,
                    580:         * closing out the interim scopes.
                    581:         *
                    582:         * If there's a nested token on the chain, then raise an error
                    583:         * as nested tokens have corresponding "ending" tokens and we're
                    584:         * breaking their scope.
                    585:         */
                    586:
                    587:        node = NULL;
                    588:
                    589:        if (ROFF_LAYOUT == tokens[tok].type &&
                    590:                        ! (ROFF_NESTED & tokens[tok].flags)) {
                    591:                for (node = tree->last; node; node = node->parent) {
                    592:                        if (node->tok == tok)
                    593:                                break;
                    594:
                    595:                        /* Don't break nested scope. */
                    596:
                    597:                        if ( ! (ROFF_NESTED & tokens[node->tok].flags))
                    598:                                continue;
                    599:                        warnx("%s: scope of %s (line %zu) broken by "
                    600:                                        "%s (line %zu)",
                    601:                                        tree->rbuf->name,
1.4       kristaps  602:                                        toknames[tok], node->line,
                    603:                                        toknames[node->tok],
1.1       kristaps  604:                                        tree->rbuf->line);
                    605:                        return(0);
                    606:                }
                    607:        }
                    608:
                    609:        if (node) {
                    610:                assert(ROFF_LAYOUT == tokens[tok].type);
                    611:                assert( ! (ROFF_NESTED & tokens[tok].flags));
                    612:                assert(node->tok == tok);
                    613:
                    614:                /* Clear up to last scoped token. */
                    615:
                    616:                /* LINTED */
                    617:                do {
                    618:                        t = tree->last->tok;
                    619:                        if ( ! (*tokens[tree->last->tok].cb)
1.2       kristaps  620:                                        (tree->last->tok, tree, NULL, ROFF_EXIT))
1.1       kristaps  621:                                return(0);
                    622:                } while (t != tok);
                    623:        }
                    624:
                    625:        /* Proceed with actual token processing. */
                    626:
                    627:        argvp = (const char **)&argv[1];
1.2       kristaps  628:        return((*tokens[tok].cb)(tok, tree, argvp, ROFF_ENTER));
1.1       kristaps  629: }
                    630:
                    631:
                    632: static int
                    633: rofffindarg(const char *name)
                    634: {
                    635:        size_t           i;
                    636:
                    637:        /* FIXME: use a table, this is slow but ok for now. */
                    638:
                    639:        /* LINTED */
                    640:        for (i = 0; i < ROFF_ARGMAX; i++)
                    641:                /* LINTED */
1.4       kristaps  642:                if (0 == strcmp(name, tokargnames[i]))
1.1       kristaps  643:                        return((int)i);
                    644:
                    645:        return(ROFF_ARGMAX);
                    646: }
                    647:
                    648:
                    649: static int
                    650: rofffindtok(const char *name)
                    651: {
                    652:        size_t           i;
                    653:
                    654:        /* FIXME: use a table, this is slow but ok for now. */
                    655:
                    656:        /* LINTED */
                    657:        for (i = 0; i < ROFF_MAX; i++)
                    658:                /* LINTED */
1.4       kristaps  659:                if (0 == strncmp(name, toknames[i], 2))
1.1       kristaps  660:                        return((int)i);
                    661:
                    662:        return(ROFF_MAX);
                    663: }
                    664:
                    665:
1.2       kristaps  666: static int
                    667: rofffindcallable(const char *name)
                    668: {
                    669:        int              c;
                    670:
                    671:        if (ROFF_MAX == (c = rofffindtok(name)))
                    672:                return(ROFF_MAX);
                    673:        return(ROFF_CALLABLE & tokens[c].flags ? c : ROFF_MAX);
                    674: }
                    675:
                    676:
1.1       kristaps  677: static struct roffnode *
1.4       kristaps  678: roffnode_new(int tokid, struct rofftree *tree)
1.1       kristaps  679: {
                    680:        struct roffnode *p;
                    681:
                    682:        if (NULL == (p = malloc(sizeof(struct roffnode)))) {
                    683:                warn("malloc");
                    684:                return(NULL);
                    685:        }
                    686:
1.4       kristaps  687:        p->line = tree->rbuf->line;
1.1       kristaps  688:        p->tok = tokid;
                    689:        p->parent = tree->last;
                    690:        tree->last = p;
                    691:        return(p);
                    692: }
                    693:
                    694:
1.5     ! kristaps  695: static int
        !           696: roffargok(int tokid, int argid)
        !           697: {
        !           698:        const int       *c;
        !           699:
        !           700:        if (NULL == (c = tokens[tokid].args))
        !           701:                return(0);
        !           702:
        !           703:        for ( ; ROFF_ARGMAX != *c; c++)
        !           704:                if (argid == *c)
        !           705:                        return(1);
        !           706:
        !           707:        return(0);
        !           708: }
        !           709:
        !           710:
1.1       kristaps  711: static void
                    712: roffnode_free(int tokid, struct rofftree *tree)
                    713: {
                    714:        struct roffnode *p;
                    715:
                    716:        assert(tree->last);
                    717:        assert(tree->last->tok == tokid);
                    718:
                    719:        p = tree->last;
                    720:        tree->last = tree->last->parent;
                    721:        free(p);
                    722: }
                    723:
                    724:
                    725: /* ARGSUSED */
                    726: static int
                    727: roff_Dd(ROFFCALL_ARGS)
                    728: {
                    729:
1.4       kristaps  730:        if (ROFF_BODY & tree->state) {
                    731:                assert( ! (ROFF_PRELUDE & tree->state));
                    732:                assert(ROFF_PRELUDE_Dd & tree->state);
                    733:                return(roff_text(tok, tree, argv, type));
                    734:        }
                    735:
1.1       kristaps  736:        assert(ROFF_PRELUDE & tree->state);
1.4       kristaps  737:        assert( ! (ROFF_BODY & tree->state));
                    738:
                    739:        if (ROFF_PRELUDE_Dd & tree->state ||
                    740:                        ROFF_PRELUDE_Dt & tree->state) {
1.1       kristaps  741:                warnx("%s: prelude `Dd' out-of-order (line %zu)",
                    742:                                tree->rbuf->name, tree->rbuf->line);
                    743:                return(0);
                    744:        }
                    745:
1.4       kristaps  746:        /* TODO: parse date. */
                    747:
1.1       kristaps  748:        assert(NULL == tree->last);
                    749:        tree->state |= ROFF_PRELUDE_Dd;
                    750:
                    751:        return(1);
                    752: }
                    753:
                    754:
                    755: /* ARGSUSED */
                    756: static int
                    757: roff_Dt(ROFFCALL_ARGS)
                    758: {
                    759:
1.4       kristaps  760:        if (ROFF_BODY & tree->state) {
                    761:                assert( ! (ROFF_PRELUDE & tree->state));
                    762:                assert(ROFF_PRELUDE_Dt & tree->state);
                    763:                return(roff_text(tok, tree, argv, type));
                    764:        }
                    765:
1.1       kristaps  766:        assert(ROFF_PRELUDE & tree->state);
1.4       kristaps  767:        assert( ! (ROFF_BODY & tree->state));
                    768:
1.1       kristaps  769:        if ( ! (ROFF_PRELUDE_Dd & tree->state) ||
                    770:                        (ROFF_PRELUDE_Dt & tree->state)) {
                    771:                warnx("%s: prelude `Dt' out-of-order (line %zu)",
                    772:                                tree->rbuf->name, tree->rbuf->line);
                    773:                return(0);
                    774:        }
                    775:
1.4       kristaps  776:        /* TODO: parse date. */
                    777:
1.1       kristaps  778:        assert(NULL == tree->last);
                    779:        tree->state |= ROFF_PRELUDE_Dt;
                    780:
                    781:        return(1);
                    782: }
                    783:
                    784:
                    785: /* ARGSUSED */
                    786: static int
                    787: roff_Os(ROFFCALL_ARGS)
                    788: {
                    789:
                    790:        if (ROFF_EXIT == type) {
1.4       kristaps  791:                assert(ROFF_PRELUDE_Os & tree->state);
                    792:                return(roff_layout(tok, tree, argv, type));
                    793:        } else if (ROFF_BODY & tree->state) {
                    794:                assert( ! (ROFF_PRELUDE & tree->state));
                    795:                assert(ROFF_PRELUDE_Os & tree->state);
                    796:                return(roff_text(tok, tree, argv, type));
                    797:        }
1.1       kristaps  798:
                    799:        assert(ROFF_PRELUDE & tree->state);
                    800:        if ( ! (ROFF_PRELUDE_Dt & tree->state) ||
                    801:                        ! (ROFF_PRELUDE_Dd & tree->state)) {
                    802:                warnx("%s: prelude `Os' out-of-order (line %zu)",
                    803:                                tree->rbuf->name, tree->rbuf->line);
                    804:                return(0);
                    805:        }
                    806:
1.4       kristaps  807:        /* TODO: extract OS. */
1.1       kristaps  808:
                    809:        tree->state |= ROFF_PRELUDE_Os;
                    810:        tree->state &= ~ROFF_PRELUDE;
                    811:        tree->state |= ROFF_BODY;
                    812:
1.4       kristaps  813:        assert(NULL == tree->last);
                    814:
                    815:        return(roff_layout(tok, tree, argv, type));
1.1       kristaps  816: }
                    817:
                    818:
1.2       kristaps  819: /* ARGUSED */
1.1       kristaps  820: static int
1.5     ! kristaps  821: roffnextopt(int tok, const char ***in, char **val)
1.1       kristaps  822: {
                    823:        const char      *arg, **argv;
                    824:        int              v;
                    825:
                    826:        *val = NULL;
                    827:        argv = *in;
                    828:        assert(argv);
                    829:
                    830:        if (NULL == (arg = *argv))
                    831:                return(-1);
                    832:        if ('-' != *arg)
                    833:                return(-1);
1.5     ! kristaps  834:
        !           835:        /* FIXME: should we let this slide... ? */
        !           836:
1.1       kristaps  837:        if (ROFF_ARGMAX == (v = rofffindarg(&arg[1])))
                    838:                return(-1);
1.5     ! kristaps  839:
        !           840:        /* FIXME: should we let this slide... ? */
        !           841:
        !           842:        if ( ! roffargok(tok, v))
        !           843:                return(-1);
1.1       kristaps  844:        if ( ! (ROFF_VALUE & tokenargs[v].flags))
                    845:                return(v);
                    846:
                    847:        *in = ++argv;
                    848:
                    849:        /* FIXME: what if this looks like a roff token or argument? */
                    850:
                    851:        return(*argv ? v : ROFF_ARGMAX);
                    852: }
                    853:
                    854:
                    855: /* ARGSUSED */
                    856: static int
1.2       kristaps  857: roff_layout(ROFFCALL_ARGS)
1.1       kristaps  858: {
1.2       kristaps  859:        int              i, c, argcp[ROFF_MAXARG];
                    860:        char            *v, *argvp[ROFF_MAXARG];
1.1       kristaps  861:
1.4       kristaps  862:        if (ROFF_PRELUDE & tree->state) {
                    863:                warnx("%s: macro `%s' called in prelude (line %zu)",
1.5     ! kristaps  864:                                tree->rbuf->name,
        !           865:                                toknames[tok],
1.4       kristaps  866:                                tree->rbuf->line);
                    867:                return(0);
                    868:        }
                    869:
1.2       kristaps  870:        if (ROFF_EXIT == type) {
                    871:                roffnode_free(tok, tree);
1.4       kristaps  872:                return((*tree->roffblkout)(tok));
1.2       kristaps  873:        }
1.1       kristaps  874:
1.2       kristaps  875:        i = 0;
1.5     ! kristaps  876:
        !           877:        while (-1 != (c = roffnextopt(tok, &argv, &v))) {
1.2       kristaps  878:                if (ROFF_ARGMAX == c) {
1.5     ! kristaps  879:                        warnx("%s: error parsing `%s' args (line %zu)",
        !           880:                                        tree->rbuf->name,
        !           881:                                        toknames[tok],
        !           882:                                        tree->rbuf->line);
        !           883:                        return(0);
        !           884:                } else if ( ! roffargok(tok, c)) {
        !           885:                        warnx("%s: arg `%s' not for `%s' (line %zu)",
1.1       kristaps  886:                                        tree->rbuf->name,
1.5     ! kristaps  887:                                        tokargnames[c],
1.4       kristaps  888:                                        toknames[tok],
1.1       kristaps  889:                                        tree->rbuf->line);
                    890:                        return(0);
                    891:                }
1.2       kristaps  892:                argcp[i] = c;
                    893:                argvp[i] = v;
1.5     ! kristaps  894:                i++;
1.1       kristaps  895:                argv++;
                    896:        }
                    897:
1.5     ! kristaps  898:        argcp[i] = ROFF_ARGMAX;
        !           899:        argvp[i] = NULL;
        !           900:
1.4       kristaps  901:        if (NULL == roffnode_new(tok, tree))
1.2       kristaps  902:                return(0);
                    903:
1.4       kristaps  904:        if ( ! (*tree->roffin)(tok, argcp, argvp))
1.2       kristaps  905:                return(0);
                    906:
                    907:        if ( ! (ROFF_PARSED & tokens[tok].flags)) {
                    908:                /* TODO: print all tokens. */
                    909:
1.4       kristaps  910:                if ( ! ((*tree->roffout)(tok)))
1.2       kristaps  911:                        return(0);
1.4       kristaps  912:                return((*tree->roffblkin)(tok));
1.2       kristaps  913:        }
                    914:
1.1       kristaps  915:        while (*argv) {
1.2       kristaps  916:                if (2 >= strlen(*argv) && ROFF_MAX !=
                    917:                                (c = rofffindcallable(*argv)))
                    918:                        if ( ! (*tokens[c].cb)(c, tree,
                    919:                                                argv + 1, ROFF_ENTER))
                    920:                                return(0);
                    921:
                    922:                /* TODO: print token. */
                    923:                argv++;
                    924:        }
                    925:
1.4       kristaps  926:        if ( ! ((*tree->roffout)(tok)))
1.2       kristaps  927:                return(0);
                    928:
1.4       kristaps  929:        return((*tree->roffblkin)(tok));
1.2       kristaps  930: }
                    931:
                    932:
                    933: /* ARGSUSED */
                    934: static int
                    935: roff_text(ROFFCALL_ARGS)
                    936: {
                    937:        int              i, c, argcp[ROFF_MAXARG];
                    938:        char            *v, *argvp[ROFF_MAXARG];
                    939:
1.4       kristaps  940:        if (ROFF_PRELUDE & tree->state) {
                    941:                warnx("%s: macro `%s' called in prelude (line %zu)",
1.5     ! kristaps  942:                                tree->rbuf->name,
        !           943:                                toknames[tok],
1.4       kristaps  944:                                tree->rbuf->line);
                    945:                return(0);
                    946:        }
                    947:
1.2       kristaps  948:        i = 0;
1.5     ! kristaps  949:
        !           950:        while (-1 != (c = roffnextopt(tok, &argv, &v))) {
1.2       kristaps  951:                if (ROFF_ARGMAX == c) {
1.5     ! kristaps  952:                        warnx("%s: error parsing `%s' args (line %zu)",
1.2       kristaps  953:                                        tree->rbuf->name,
1.4       kristaps  954:                                        toknames[tok],
1.2       kristaps  955:                                        tree->rbuf->line);
                    956:                        return(0);
1.5     ! kristaps  957:                }
1.2       kristaps  958:                argcp[i] = c;
                    959:                argvp[i] = v;
1.5     ! kristaps  960:                i++;
1.1       kristaps  961:                argv++;
                    962:        }
                    963:
1.5     ! kristaps  964:        argcp[i] = ROFF_ARGMAX;
        !           965:        argvp[i] = NULL;
        !           966:
1.4       kristaps  967:        if ( ! (*tree->roffin)(tok, argcp, argvp))
1.2       kristaps  968:                return(0);
1.1       kristaps  969:
1.2       kristaps  970:        if ( ! (ROFF_PARSED & tokens[tok].flags)) {
                    971:                /* TODO: print all tokens. */
1.4       kristaps  972:                return((*tree->roffout)(tok));
1.2       kristaps  973:        }
1.1       kristaps  974:
1.2       kristaps  975:        while (*argv) {
                    976:                if (2 >= strlen(*argv) && ROFF_MAX !=
                    977:                                (c = rofffindcallable(*argv)))
                    978:                        if ( ! (*tokens[c].cb)(c, tree,
                    979:                                                argv + 1, ROFF_ENTER))
                    980:                                return(0);
                    981:
                    982:                /* TODO: print token. */
                    983:                argv++;
                    984:        }
                    985:
1.4       kristaps  986:        return((*tree->roffout)(tok));
1.1       kristaps  987: }

CVSweb