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

Annotation of mandoc/mdocterm.c, Revision 1.49

1.49    ! kristaps    1: /* $Id: mdocterm.c,v 1.48 2009/03/17 13:35:46 kristaps Exp $ */
1.1       kristaps    2: /*
1.47      kristaps    3:  * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@openbsd.org>
1.1       kristaps    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:  */
1.37      kristaps   19: #include <sys/types.h>
1.49    ! kristaps   20: #include <sys/stat.h>
1.37      kristaps   21:
1.1       kristaps   22: #include <assert.h>
                     23: #include <err.h>
1.49    ! kristaps   24: #include <fcntl.h>
1.1       kristaps   25: #include <getopt.h>
1.3       kristaps   26: #include <stdio.h>
1.1       kristaps   27: #include <stdlib.h>
1.3       kristaps   28: #include <string.h>
1.43      kristaps   29: #include <unistd.h>
1.5       kristaps   30:
1.1       kristaps   31: #include "term.h"
                     32:
1.49    ! kristaps   33: #define        WARN_WALL         0x03          /* All-warnings mask. */
        !            34: #define        WARN_WCOMPAT     (1 << 0)       /* Compatibility warnings. */
        !            35: #define        WARN_WSYNTAX     (1 << 1)       /* Syntax warnings. */
        !            36: #define        WARN_WERR        (1 << 2)       /* Warnings->errors. */
        !            37:
        !            38: enum   termt {
        !            39:        TERMT_ASCII,
        !            40:        TERMT_LINT,
        !            41:        TERMT_TREE
1.43      kristaps   42: };
                     43:
1.49    ! kristaps   44: extern char             *__progname;
        !            45:
        !            46: __dead static void       version(void);
        !            47: __dead static void       usage(void);
        !            48: #if 0
        !            49: __dead static void       punt(struct cmdargs *, char **);
        !            50: #endif
        !            51: static int               foptions(int *, char *);
        !            52: static int               toptions(enum termt *, char *);
        !            53: static int               woptions(int *, char *);
        !            54: static int               merr(void *, int, int, const char *);
        !            55: static int               mwarn(void *, int, int,
        !            56:                                enum mdoc_warn, const char *);
        !            57: static void              body(struct termp *, struct termpair *,
1.3       kristaps   58:                                const struct mdoc_meta *,
                     59:                                const struct mdoc_node *);
                     60: static void              header(struct termp *,
                     61:                                const struct mdoc_meta *);
                     62: static void              footer(struct termp *,
                     63:                                const struct mdoc_meta *);
1.49    ! kristaps   64: static int               file(char **, size_t *, char **, size_t *,
        !            65:                                const char *, struct mdoc *);
        !            66: static int               fdesc(char **, size_t *, char **, size_t *,
        !            67:                                const char *, int, struct mdoc *);
        !            68: static void              pword(struct termp *, const char *, int);
        !            69: static void              pescape(struct termp *,
        !            70:                                const char *, int *, int);
1.25      kristaps   71: static void              nescape(struct termp *,
1.12      kristaps   72:                                const char *, size_t);
1.3       kristaps   73: static void              chara(struct termp *, char);
1.25      kristaps   74: static void              stringa(struct termp *,
                     75:                                const char *, size_t);
1.38      kristaps   76: static void              sanity(const struct mdoc_node *);
1.3       kristaps   77:
1.49    ! kristaps   78:
1.1       kristaps   79: int
                     80: main(int argc, char *argv[])
                     81: {
1.49    ! kristaps   82:        struct termp     termp;
        !            83:        int              c, fflags, wflags;
        !            84:        struct mdoc_cb   cb;
        !            85:        struct mdoc     *mdoc;
        !            86:        char            *buf, *line;
        !            87:        size_t           bufsz, linesz;
        !            88:        enum termt       termt;
        !            89:
        !            90:        bzero(&termp, sizeof(struct termp));
        !            91:        bzero(&cb, sizeof(struct mdoc_cb));
        !            92:
        !            93:        termt = TERMT_ASCII;
        !            94:        fflags = wflags = 0;
        !            95:
        !            96:        /* LINTED */
        !            97:        while (-1 != (c = getopt(argc, argv, "f:VW:T:")))
        !            98:                switch (c) {
        !            99:                case ('f'):
        !           100:                        if ( ! foptions(&fflags, optarg))
        !           101:                                return(0);
        !           102:                        break;
        !           103:                case ('T'):
        !           104:                        if ( ! toptions(&termt, optarg))
        !           105:                                return(0);
        !           106:                        break;
        !           107:                case ('W'):
        !           108:                        if ( ! woptions(&wflags, optarg))
        !           109:                                return(0);
        !           110:                        break;
        !           111:                case ('V'):
        !           112:                        version();
        !           113:                        /* NOTREACHED */
        !           114:                default:
        !           115:                        usage();
        !           116:                        /* NOTREACHED */
        !           117:                }
        !           118:
        !           119:        argc -= optind;
        !           120:        argv += optind;
        !           121:
        !           122:        termp.maxrmargin = 78;                  /* FIXME */
        !           123:
        !           124:        cb.mdoc_err = merr;
        !           125:        cb.mdoc_warn = mwarn;
        !           126:
        !           127:        /* Line and block buffers persist between parses. */
        !           128:
        !           129:        buf = line = NULL;
        !           130:        bufsz = linesz = 0;
        !           131:
        !           132:        /* Overall mdoc persists between parses. */
        !           133:
        !           134:        mdoc = mdoc_alloc(&wflags, fflags, &cb);
        !           135:
        !           136:        while (*argv) {
        !           137:                if ( ! file(&line, &linesz, &buf, &bufsz, *argv, mdoc))
        !           138:                        break;
        !           139:
        !           140:                switch (termt) {
        !           141:                case (TERMT_ASCII):
        !           142:                        if (NULL == termp.symtab)
        !           143:                                termp.symtab = ascii2htab();
        !           144:                        header(&termp, mdoc_meta(mdoc));
        !           145:                        body(&termp, NULL, mdoc_meta(mdoc),
        !           146:                                        mdoc_node(mdoc));
        !           147:                        footer(&termp, mdoc_meta(mdoc));
        !           148:                        break;
        !           149:                default:
        !           150:                        break;
        !           151:                }
        !           152:
        !           153:                mdoc_reset(mdoc);
        !           154:                argv++;
1.43      kristaps  155:        }
1.3       kristaps  156:
1.49    ! kristaps  157:        if (buf)
        !           158:                free(buf);
        !           159:        if (line)
        !           160:                free(line);
        !           161:        if (termp.buf)
        !           162:                free(termp.buf);
        !           163:        if (termp.symtab)
        !           164:                asciifree(termp.symtab);
        !           165:
        !           166:        mdoc_free(mdoc);
        !           167:
        !           168:        return(0);
        !           169: }
        !           170:
        !           171:
        !           172: __dead static void
        !           173: version(void)
        !           174: {
        !           175:
        !           176:        (void)printf("%s %s\n", __progname, VERSION);
        !           177:        exit(0);
        !           178:        /* NOTREACHED */
        !           179: }
1.3       kristaps  180:
                    181:
1.49    ! kristaps  182: __dead static void
        !           183: usage(void)
        !           184: {
1.3       kristaps  185:
1.49    ! kristaps  186:        (void)fprintf(stderr, "usage: %s\n", __progname);
        !           187:        exit(1);
1.3       kristaps  188:        /* NOTREACHED */
                    189: }
                    190:
                    191:
1.43      kristaps  192: static int
1.49    ! kristaps  193: file(char **ln, size_t *lnsz, char **buf, size_t *bufsz,
        !           194:                const char *file, struct mdoc *mdoc)
        !           195: {
        !           196:        int              fd, c;
        !           197:
        !           198:        if (-1 == (fd = open(file, O_RDONLY, 0))) {
        !           199:                warn("%s", file);
        !           200:                return(0);
        !           201:        }
        !           202:
        !           203:        c = fdesc(ln, lnsz, buf, bufsz, file, fd, mdoc);
        !           204:
        !           205:        if (-1 == close(fd))
        !           206:                warn("%s", file);
        !           207:
        !           208:        return(c);
        !           209: }
        !           210:
        !           211:
        !           212: static int
        !           213: fdesc(char **lnp, size_t *lnsz, char **bufp, size_t *bufsz,
        !           214:                const char *f, int fd, struct mdoc *mdoc)
        !           215: {
        !           216:        size_t           sz;
        !           217:        ssize_t          ssz;
        !           218:        struct stat      st;
        !           219:        int              j, i, pos, lnn;
        !           220:        char            *ln, *buf;
        !           221:
        !           222:        buf = *bufp;
        !           223:        ln = *lnp;
        !           224:
        !           225:        /*
        !           226:         * Two buffers: ln and buf.  buf is the input buffer, optimised
        !           227:         * for each file's block size.  ln is a line buffer.  Both
        !           228:         * growable, hence passed in by ptr-ptr.
        !           229:         */
        !           230:
        !           231:        if (-1 == fstat(fd, &st)) {
        !           232:                warnx("%s", f);
        !           233:                sz = BUFSIZ;
        !           234:        } else
        !           235:                sz = (unsigned)BUFSIZ > st.st_blksize ?
        !           236:                        (size_t)BUFSIZ : st.st_blksize;
        !           237:
        !           238:        if (sz > *bufsz) {
        !           239:                if (NULL == (buf = realloc(buf, sz)))
        !           240:                        err(1, "realloc");
        !           241:                *bufp = buf;
        !           242:                *bufsz = sz;
        !           243:        }
        !           244:
        !           245:        /*
        !           246:         * Fill buf with file blocksize and parse newlines into ln.
        !           247:         */
        !           248:
        !           249:        for (lnn = 1, pos = 0; ; ) {
        !           250:                if (-1 == (ssz = read(fd, buf, sz))) {
        !           251:                        warn("%s", f);
        !           252:                        return(0);
        !           253:                } else if (0 == ssz)
        !           254:                        break;
        !           255:
        !           256:                for (i = 0; i < (int)ssz; i++) {
        !           257:                        if (pos >= (int)*lnsz) {
        !           258:                                *lnsz += 256; /* Step-size. */
        !           259:                                ln = realloc(ln, *lnsz);
        !           260:                                if (NULL == ln)
        !           261:                                        err(1, "realloc");
        !           262:                                *lnp = ln;
        !           263:                        }
        !           264:
        !           265:                        if ('\n' != buf[i]) {
        !           266:                                ln[pos++] = buf[i];
        !           267:                                continue;
        !           268:                        }
        !           269:
        !           270:                        /* Check for CPP-escaped newline.  */
        !           271:
        !           272:                        if (pos > 0 && '\\' == ln[pos - 1]) {
        !           273:                                for (j = pos - 1; j >= 0; j--)
        !           274:                                        if ('\\' != ln[j])
        !           275:                                                break;
        !           276:
        !           277:                                if ( ! ((pos - j) % 2)) {
        !           278:                                        pos--;
        !           279:                                        lnn++;
        !           280:                                        continue;
        !           281:                                }
        !           282:                        }
        !           283:
        !           284:                        ln[pos] = 0;
        !           285:                        if ( ! mdoc_parseln(mdoc, lnn, ln))
        !           286:                                return(0);
        !           287:                        lnn++;
        !           288:                        pos = 0;
        !           289:                }
        !           290:        }
        !           291:
        !           292:        return(mdoc_endparse(mdoc));
        !           293: }
        !           294:
        !           295:
        !           296: static int
        !           297: toptions(enum termt *tflags, char *arg)
        !           298: {
        !           299:
        !           300:        if (0 == strcmp(arg, "ascii"))
        !           301:                *tflags = TERMT_ASCII;
        !           302:        else if (0 == strcmp(arg, "lint"))
        !           303:                *tflags = TERMT_LINT;
        !           304:        else if (0 == strcmp(arg, "tree"))
        !           305:                *tflags = TERMT_TREE;
        !           306:        else {
        !           307:                warnx("bad argument: -T%s", arg);
        !           308:                return(0);
        !           309:        }
        !           310:
        !           311:        return(1);
        !           312: }
        !           313:
        !           314:
        !           315: /*
        !           316:  * Parse out the options for [-fopt...] setting compiler options.  These
        !           317:  * can be comma-delimited or called again.
        !           318:  */
        !           319: static int
        !           320: foptions(int *fflags, char *arg)
1.43      kristaps  321: {
                    322:        char            *v;
1.49    ! kristaps  323:        char            *toks[] = { "ign-scope", "ign-escape",
        !           324:                                    "ign-macro", NULL };
1.43      kristaps  325:
                    326:        while (*arg)
                    327:                switch (getsubopt(&arg, toks, &v)) {
                    328:                case (0):
1.49    ! kristaps  329:                        *fflags |= MDOC_IGN_SCOPE;
        !           330:                        break;
        !           331:                case (1):
        !           332:                        *fflags |= MDOC_IGN_ESCAPE;
        !           333:                        break;
        !           334:                case (2):
        !           335:                        *fflags |= MDOC_IGN_MACRO;
1.43      kristaps  336:                        break;
                    337:                default:
1.49    ! kristaps  338:                        warnx("bad argument: -f%s", arg);
1.43      kristaps  339:                        return(0);
                    340:                }
                    341:
                    342:        return(1);
                    343: }
                    344:
                    345:
1.49    ! kristaps  346: /*
        !           347:  * Parse out the options for [-Werr...], which sets warning modes.
        !           348:  * These can be comma-delimited or called again.  XXX - should this be
        !           349:  * using -w like troff?
        !           350:  */
1.43      kristaps  351: static int
1.49    ! kristaps  352: woptions(int *wflags, char *arg)
1.42      kristaps  353: {
1.49    ! kristaps  354:        char            *v;
        !           355:        char            *toks[] = { "all", "compat",
        !           356:                        "syntax", "error", NULL };
1.42      kristaps  357:
1.49    ! kristaps  358:        while (*arg)
        !           359:                switch (getsubopt(&arg, toks, &v)) {
        !           360:                case (0):
        !           361:                        *wflags |= WARN_WALL;
        !           362:                        break;
        !           363:                case (1):
        !           364:                        *wflags |= WARN_WCOMPAT;
        !           365:                        break;
        !           366:                case (2):
        !           367:                        *wflags |= WARN_WSYNTAX;
        !           368:                        break;
        !           369:                case (3):
        !           370:                        *wflags |= WARN_WERR;
        !           371:                        break;
        !           372:                default:
        !           373:                        warnx("bad argument: -W%s", arg);
        !           374:                        return(0);
        !           375:                }
1.42      kristaps  376:
1.43      kristaps  377:        return(1);
1.42      kristaps  378: }
                    379:
                    380:
1.25      kristaps  381: /*
                    382:  * Flush a line of text.  A "line" is loosely defined as being something
                    383:  * that should be followed by a newline, regardless of whether it's
                    384:  * broken apart by newlines getting there.  A line can also be a
                    385:  * fragment of a columnar list.
                    386:  *
                    387:  * Specifically, a line is whatever's in p->buf of length p->col, which
                    388:  * is zeroed after this function returns.
                    389:  *
                    390:  * The variables TERMP_NOLPAD, TERMP_LITERAL and TERMP_NOBREAK are of
                    391:  * critical importance here.  Their behaviour follows:
                    392:  *
                    393:  *  - TERMP_NOLPAD: when beginning to write the line, don't left-pad the
                    394:  *    offset value.  This is useful when doing columnar lists where the
                    395:  *    prior column has right-padded.
                    396:  *
                    397:  *  - TERMP_NOBREAK: this is the most important and is used when making
                    398:  *    columns.  In short: don't print a newline and instead pad to the
                    399:  *    right margin.  Used in conjunction with TERMP_NOLPAD.
                    400:  *
1.49    ! kristaps  401:  *  - TERMP_NONOBREAK: don't newline when TERMP_NOBREAK is specified.
        !           402:  *
1.25      kristaps  403:  *  In-line line breaking:
                    404:  *
                    405:  *  If TERMP_NOBREAK is specified and the line overruns the right
                    406:  *  margin, it will break and pad-right to the right margin after
                    407:  *  writing.  If maxrmargin is violated, it will break and continue
                    408:  *  writing from the right-margin, which will lead to the above
                    409:  *  scenario upon exit.
                    410:  *
                    411:  *  Otherwise, the line will break at the right margin.  Extremely long
                    412:  *  lines will cause the system to emit a warning (TODO: hyphenate, if
                    413:  *  possible).
                    414:  */
1.3       kristaps  415: void
                    416: flushln(struct termp *p)
                    417: {
1.49    ! kristaps  418:        int              i, j;
        !           419:        size_t           vsz, vis, maxvis, mmax, bp;
1.3       kristaps  420:
                    421:        /*
                    422:         * First, establish the maximum columns of "visible" content.
                    423:         * This is usually the difference between the right-margin and
                    424:         * an indentation, but can be, for tagged lists or columns, a
                    425:         * small set of values.
                    426:         */
                    427:
                    428:        assert(p->offset < p->rmargin);
                    429:        maxvis = p->rmargin - p->offset;
1.25      kristaps  430:        mmax = p->maxrmargin - p->offset;
                    431:        bp = TERMP_NOBREAK & p->flags ? mmax : maxvis;
1.3       kristaps  432:        vis = 0;
                    433:
                    434:        /*
                    435:         * If in the standard case (left-justified), then begin with our
                    436:         * indentation, otherwise (columns, etc.) just start spitting
                    437:         * out text.
                    438:         */
                    439:
                    440:        if ( ! (p->flags & TERMP_NOLPAD))
                    441:                /* LINTED */
1.49    ! kristaps  442:                for (j = 0; j < (int)p->offset; j++)
1.3       kristaps  443:                        putchar(' ');
                    444:
1.49    ! kristaps  445:        for (i = 0; i < (int)p->col; i++) {
1.3       kristaps  446:                /*
                    447:                 * Count up visible word characters.  Control sequences
1.23      kristaps  448:                 * (starting with the CSI) aren't counted.  A space
                    449:                 * generates a non-printing word, which is valid (the
                    450:                 * space is printed according to regular spacing rules).
1.3       kristaps  451:                 */
                    452:
                    453:                /* LINTED */
1.49    ! kristaps  454:                for (j = i, vsz = 0; j < (int)p->col; j++) {
1.46      kristaps  455:                        if (' ' == p->buf[j])
1.3       kristaps  456:                                break;
1.45      kristaps  457:                        else if (8 == p->buf[j])
1.42      kristaps  458:                                j += 1;
1.45      kristaps  459:                        else
1.3       kristaps  460:                                vsz++;
                    461:                }
                    462:
                    463:                /*
1.25      kristaps  464:                 * Do line-breaking.  If we're greater than our
                    465:                 * break-point and already in-line, break to the next
                    466:                 * line and start writing.  If we're at the line start,
                    467:                 * then write out the word (TODO: hyphenate) and break
                    468:                 * in a subsequent loop invocation.
1.3       kristaps  469:                 */
                    470:
1.22      kristaps  471:                if ( ! (TERMP_NOBREAK & p->flags)) {
1.25      kristaps  472:                        if (vis && vis + vsz > bp) {
1.22      kristaps  473:                                putchar('\n');
1.49    ! kristaps  474:                                for (j = 0; j < (int)p->offset; j++)
1.22      kristaps  475:                                        putchar(' ');
                    476:                                vis = 0;
1.25      kristaps  477:                        } else if (vis + vsz > bp)
                    478:                                warnx("word breaks right margin");
                    479:
                    480:                        /* TODO: hyphenate. */
                    481:
                    482:                } else {
                    483:                        if (vis && vis + vsz > bp) {
                    484:                                putchar('\n');
1.49    ! kristaps  485:                                for (j = 0; j < (int)p->rmargin; j++)
1.25      kristaps  486:                                        putchar(' ');
1.30      kristaps  487:                                vis = p->rmargin - p->offset;
1.25      kristaps  488:                        } else if (vis + vsz > bp)
                    489:                                warnx("word breaks right margin");
                    490:
                    491:                        /* TODO: hyphenate. */
1.24      kristaps  492:                }
1.3       kristaps  493:
                    494:                /*
                    495:                 * Write out the word and a trailing space.  Omit the
1.25      kristaps  496:                 * space if we're the last word in the line or beyond
                    497:                 * our breakpoint.
1.3       kristaps  498:                 */
                    499:
1.49    ! kristaps  500:                for ( ; i < (int)p->col; i++) {
1.46      kristaps  501:                        if (' ' == p->buf[i])
1.3       kristaps  502:                                break;
                    503:                        putchar(p->buf[i]);
                    504:                }
                    505:                vis += vsz;
1.49    ! kristaps  506:                if (i < (int)p->col && vis <= bp) {
1.3       kristaps  507:                        putchar(' ');
                    508:                        vis++;
                    509:                }
                    510:        }
                    511:
1.25      kristaps  512:        /*
                    513:         * If we've overstepped our maximum visible no-break space, then
                    514:         * cause a newline and offset at the right margin.
                    515:         */
                    516:
1.22      kristaps  517:        if ((TERMP_NOBREAK & p->flags) && vis >= maxvis) {
1.32      kristaps  518:                if ( ! (TERMP_NONOBREAK & p->flags)) {
                    519:                        putchar('\n');
1.49    ! kristaps  520:                        for (i = 0; i < (int)p->rmargin; i++)
1.32      kristaps  521:                                putchar(' ');
                    522:                }
1.22      kristaps  523:                p->col = 0;
                    524:                return;
                    525:        }
                    526:
1.3       kristaps  527:        /*
                    528:         * If we're not to right-marginalise it (newline), then instead
                    529:         * pad to the right margin and stay off.
                    530:         */
                    531:
1.32      kristaps  532:        if (p->flags & TERMP_NOBREAK) {
                    533:                if ( ! (TERMP_NONOBREAK & p->flags))
                    534:                        for ( ; vis < maxvis; vis++)
                    535:                                putchar(' ');
                    536:        } else
1.3       kristaps  537:                putchar('\n');
                    538:
                    539:        p->col = 0;
                    540: }
                    541:
                    542:
1.25      kristaps  543: /*
                    544:  * A newline only breaks an existing line; it won't assert vertical
                    545:  * space.  All data in the output buffer is flushed prior to the newline
                    546:  * assertion.
                    547:  */
1.3       kristaps  548: void
                    549: newln(struct termp *p)
                    550: {
1.1       kristaps  551:
1.3       kristaps  552:        p->flags |= TERMP_NOSPACE;
1.12      kristaps  553:        if (0 == p->col) {
                    554:                p->flags &= ~TERMP_NOLPAD;
1.3       kristaps  555:                return;
1.12      kristaps  556:        }
1.3       kristaps  557:        flushln(p);
1.11      kristaps  558:        p->flags &= ~TERMP_NOLPAD;
1.3       kristaps  559: }
                    560:
                    561:
1.25      kristaps  562: /*
                    563:  * Asserts a vertical space (a full, empty line-break between lines).
                    564:  * Note that if used twice, this will cause two blank spaces and so on.
                    565:  * All data in the output buffer is flushed prior to the newline
                    566:  * assertion.
                    567:  */
1.3       kristaps  568: void
                    569: vspace(struct termp *p)
                    570: {
                    571:
                    572:        newln(p);
                    573:        putchar('\n');
                    574: }
                    575:
                    576:
1.25      kristaps  577: /*
                    578:  * Break apart a word into "pwords" (partial-words, usually from
                    579:  * breaking up a phrase into individual words) and, eventually, put them
                    580:  * into the output buffer.  If we're a literal word, then don't break up
                    581:  * the word and put it verbatim into the output buffer.
                    582:  */
1.3       kristaps  583: void
                    584: word(struct termp *p, const char *word)
                    585: {
1.49    ! kristaps  586:        int              i, j, len;
1.3       kristaps  587:
                    588:        if (p->flags & TERMP_LITERAL) {
1.49    ! kristaps  589:                pword(p, word, (int)strlen(word));
1.3       kristaps  590:                return;
                    591:        }
                    592:
1.49    ! kristaps  593:        if (0 == (len = (int)strlen(word)))
1.40      kristaps  594:                errx(1, "blank line not in literal context");
1.3       kristaps  595:
                    596:        if (mdoc_isdelim(word)) {
                    597:                if ( ! (p->flags & TERMP_IGNDELIM))
                    598:                        p->flags |= TERMP_NOSPACE;
                    599:                p->flags &= ~TERMP_IGNDELIM;
                    600:        }
                    601:
                    602:        /* LINTED */
                    603:        for (j = i = 0; i < len; i++) {
1.46      kristaps  604:                if (' ' != word[i]) {
1.3       kristaps  605:                        j++;
                    606:                        continue;
1.20      kristaps  607:                }
                    608:
                    609:                /* Escaped spaces don't delimit... */
1.46      kristaps  610:                if (i && ' ' == word[i] && '\\' == word[i - 1]) {
1.20      kristaps  611:                        j++;
                    612:                        continue;
1.1       kristaps  613:                }
1.20      kristaps  614:
1.3       kristaps  615:                if (0 == j)
                    616:                        continue;
                    617:                assert(i >= j);
                    618:                pword(p, &word[i - j], j);
                    619:                j = 0;
                    620:        }
                    621:        if (j > 0) {
                    622:                assert(i >= j);
                    623:                pword(p, &word[i - j], j);
                    624:        }
                    625: }
                    626:
                    627:
1.25      kristaps  628: /*
                    629:  * This is the main function for printing out nodes.  It's constituted
                    630:  * of PRE and POST functions, which correspond to prefix and infix
                    631:  * processing.  The termpair structure allows data to persist between
                    632:  * prefix and postfix invocations.
                    633:  */
1.3       kristaps  634: static void
1.12      kristaps  635: body(struct termp *p, struct termpair *ppair,
                    636:                const struct mdoc_meta *meta,
1.3       kristaps  637:                const struct mdoc_node *node)
                    638: {
                    639:        int              dochild;
1.9       kristaps  640:        struct termpair  pair;
1.3       kristaps  641:
1.38      kristaps  642:        /* Some quick sanity-checking. */
                    643:
                    644:        sanity(node);
                    645:
1.3       kristaps  646:        /* Pre-processing. */
                    647:
                    648:        dochild = 1;
1.12      kristaps  649:        pair.ppair = ppair;
1.9       kristaps  650:        pair.type = 0;
1.11      kristaps  651:        pair.offset = pair.rmargin = 0;
1.10      kristaps  652:        pair.flag = 0;
1.12      kristaps  653:        pair.count = 0;
1.3       kristaps  654:
                    655:        if (MDOC_TEXT != node->type) {
                    656:                if (termacts[node->tok].pre)
1.9       kristaps  657:                        if ( ! (*termacts[node->tok].pre)(p, &pair, meta, node))
1.3       kristaps  658:                                dochild = 0;
                    659:        } else /* MDOC_TEXT == node->type */
1.35      kristaps  660:                word(p, node->string);
1.3       kristaps  661:
                    662:        /* Children. */
                    663:
1.10      kristaps  664:        if (TERMPAIR_FLAG & pair.type)
                    665:                p->flags |= pair.flag;
1.9       kristaps  666:
1.3       kristaps  667:        if (dochild && node->child)
1.12      kristaps  668:                body(p, &pair, meta, node->child);
1.3       kristaps  669:
1.10      kristaps  670:        if (TERMPAIR_FLAG & pair.type)
                    671:                p->flags &= ~pair.flag;
1.9       kristaps  672:
1.3       kristaps  673:        /* Post-processing. */
                    674:
                    675:        if (MDOC_TEXT != node->type)
                    676:                if (termacts[node->tok].post)
1.9       kristaps  677:                        (*termacts[node->tok].post)(p, &pair, meta, node);
1.3       kristaps  678:
                    679:        /* Siblings. */
1.1       kristaps  680:
1.3       kristaps  681:        if (node->next)
1.12      kristaps  682:                body(p, ppair, meta, node->next);
1.3       kristaps  683: }
                    684:
                    685:
                    686: static void
                    687: footer(struct termp *p, const struct mdoc_meta *meta)
                    688: {
                    689:        struct tm       *tm;
                    690:        char            *buf, *os;
                    691:
                    692:        if (NULL == (buf = malloc(p->rmargin)))
                    693:                err(1, "malloc");
                    694:        if (NULL == (os = malloc(p->rmargin)))
                    695:                err(1, "malloc");
                    696:
                    697:        tm = localtime(&meta->date);
                    698:
1.7       kristaps  699: #ifdef __OpenBSD__
                    700:        if (NULL == strftime(buf, p->rmargin, "%B %d, %Y", tm))
                    701: #else
1.3       kristaps  702:        if (0 == strftime(buf, p->rmargin, "%B %d, %Y", tm))
                    703: #endif
                    704:                err(1, "strftime");
                    705:
1.15      kristaps  706:        (void)strlcpy(os, meta->os, p->rmargin);
1.3       kristaps  707:
1.16      kristaps  708:        /*
                    709:         * This is /slightly/ different from regular groff output
                    710:         * because we don't have page numbers.  Print the following:
                    711:         *
                    712:         * OS                                            MDOCDATE
                    713:         */
                    714:
1.15      kristaps  715:        vspace(p);
1.3       kristaps  716:
1.15      kristaps  717:        p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
                    718:        p->rmargin = p->maxrmargin - strlen(buf);
                    719:        p->offset = 0;
1.3       kristaps  720:
1.15      kristaps  721:        word(p, os);
                    722:        flushln(p);
1.3       kristaps  723:
1.15      kristaps  724:        p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
                    725:        p->offset = p->rmargin;
                    726:        p->rmargin = p->maxrmargin;
                    727:        p->flags &= ~TERMP_NOBREAK;
                    728:
                    729:        word(p, buf);
                    730:        flushln(p);
1.1       kristaps  731:
1.3       kristaps  732:        free(buf);
                    733:        free(os);
1.1       kristaps  734: }
                    735:
                    736:
1.3       kristaps  737: static void
                    738: header(struct termp *p, const struct mdoc_meta *meta)
                    739: {
1.49    ! kristaps  740:        char            *buf, *title;
1.18      kristaps  741:
                    742:        p->rmargin = p->maxrmargin;
                    743:        p->offset = 0;
1.3       kristaps  744:
                    745:        if (NULL == (buf = malloc(p->rmargin)))
                    746:                err(1, "malloc");
                    747:        if (NULL == (title = malloc(p->rmargin)))
                    748:                err(1, "malloc");
                    749:
1.16      kristaps  750:        /*
                    751:         * The header is strange.  It has three components, which are
                    752:         * really two with the first duplicated.  It goes like this:
                    753:         *
                    754:         * IDENTIFIER              TITLE                   IDENTIFIER
                    755:         *
                    756:         * The IDENTIFIER is NAME(SECTION), which is the command-name
                    757:         * (if given, or "unknown" if not) followed by the manual page
                    758:         * section.  These are given in `Dt'.  The TITLE is a free-form
                    759:         * string depending on the manual volume.  If not specified, it
                    760:         * switches on the manual section.
                    761:         */
                    762:
1.34      kristaps  763:        assert(meta->vol);
                    764:        (void)strlcpy(buf, meta->vol, p->rmargin);
1.13      kristaps  765:
1.34      kristaps  766:        if (meta->arch) {
                    767:                (void)strlcat(buf, " (", p->rmargin);
                    768:                (void)strlcat(buf, meta->arch, p->rmargin);
                    769:                (void)strlcat(buf, ")", p->rmargin);
                    770:        }
1.13      kristaps  771:
1.34      kristaps  772:        (void)snprintf(title, p->rmargin, "%s(%d)",
                    773:                        meta->title, meta->msec);
1.13      kristaps  774:
                    775:        p->offset = 0;
                    776:        p->rmargin = (p->maxrmargin - strlen(buf)) / 2;
1.15      kristaps  777:        p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
1.3       kristaps  778:
1.13      kristaps  779:        word(p, title);
                    780:        flushln(p);
1.3       kristaps  781:
1.15      kristaps  782:        p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
1.13      kristaps  783:        p->offset = p->rmargin;
1.15      kristaps  784:        p->rmargin = p->maxrmargin - strlen(title);
1.3       kristaps  785:
1.13      kristaps  786:        word(p, buf);
                    787:        flushln(p);
1.3       kristaps  788:
1.13      kristaps  789:        p->offset = p->rmargin;
                    790:        p->rmargin = p->maxrmargin;
                    791:        p->flags &= ~TERMP_NOBREAK;
1.15      kristaps  792:        p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
1.3       kristaps  793:
1.13      kristaps  794:        word(p, title);
                    795:        flushln(p);
1.3       kristaps  796:
1.13      kristaps  797:        p->rmargin = p->maxrmargin;
                    798:        p->offset = 0;
                    799:        p->flags &= ~TERMP_NOSPACE;
1.3       kristaps  800:
                    801:        free(title);
                    802:        free(buf);
                    803: }
1.25      kristaps  804:
                    805:
                    806: /*
                    807:  * Determine the symbol indicated by an escape sequences, that is, one
                    808:  * starting with a backslash.  Once done, we pass this value into the
                    809:  * output buffer by way of the symbol table.
                    810:  */
                    811: static void
                    812: nescape(struct termp *p, const char *word, size_t len)
                    813: {
1.46      kristaps  814:        const char      *rhs;
                    815:        size_t           sz;
1.25      kristaps  816:
1.49    ! kristaps  817:        if (NULL == (rhs = a2ascii(p->symtab, word, len, &sz)))
1.27      kristaps  818:                return;
1.46      kristaps  819:        stringa(p, rhs, sz);
1.25      kristaps  820: }
                    821:
                    822:
                    823: /*
                    824:  * Handle an escape sequence: determine its length and pass it to the
                    825:  * escape-symbol look table.  Note that we assume mdoc(3) has validated
                    826:  * the escape sequence (we assert upon badly-formed escape sequences).
                    827:  */
                    828: static void
1.49    ! kristaps  829: pescape(struct termp *p, const char *word, int *i, int len)
1.25      kristaps  830: {
1.49    ! kristaps  831:        int              j;
1.25      kristaps  832:
1.49    ! kristaps  833:        if (++(*i) >= len)
1.36      kristaps  834:                return;
1.25      kristaps  835:
                    836:        if ('(' == word[*i]) {
                    837:                (*i)++;
1.49    ! kristaps  838:                if (*i + 1 >= len)
1.36      kristaps  839:                        return;
1.49    ! kristaps  840:
1.25      kristaps  841:                nescape(p, &word[*i], 2);
                    842:                (*i)++;
                    843:                return;
                    844:
                    845:        } else if ('*' == word[*i]) {
                    846:                (*i)++;
1.49    ! kristaps  847:                if (*i >= len)
1.36      kristaps  848:                        return;
1.49    ! kristaps  849:
1.25      kristaps  850:                switch (word[*i]) {
                    851:                case ('('):
                    852:                        (*i)++;
1.49    ! kristaps  853:                        if (*i + 1 >= len)
1.36      kristaps  854:                                return;
1.49    ! kristaps  855:
1.25      kristaps  856:                        nescape(p, &word[*i], 2);
                    857:                        (*i)++;
                    858:                        return;
                    859:                case ('['):
                    860:                        break;
                    861:                default:
                    862:                        nescape(p, &word[*i], 1);
                    863:                        return;
                    864:                }
                    865:
                    866:        } else if ('[' != word[*i]) {
                    867:                nescape(p, &word[*i], 1);
                    868:                return;
                    869:        }
                    870:
                    871:        (*i)++;
                    872:        for (j = 0; word[*i] && ']' != word[*i]; (*i)++, j++)
                    873:                /* Loop... */ ;
                    874:
1.49    ! kristaps  875:        if (0 == word[*i])
1.36      kristaps  876:                return;
1.49    ! kristaps  877:
        !           878:        nescape(p, &word[*i - j], (size_t)j);
1.25      kristaps  879: }
                    880:
                    881:
                    882: /*
                    883:  * Handle pwords, partial words, which may be either a single word or a
                    884:  * phrase that cannot be broken down (such as a literal string).  This
                    885:  * handles word styling.
                    886:  */
                    887: static void
1.49    ! kristaps  888: pword(struct termp *p, const char *word, int len)
1.25      kristaps  889: {
1.49    ! kristaps  890:        int              i;
1.25      kristaps  891:
                    892:        if ( ! (TERMP_NOSPACE & p->flags) &&
                    893:                        ! (TERMP_LITERAL & p->flags))
                    894:                chara(p, ' ');
                    895:
                    896:        if ( ! (p->flags & TERMP_NONOSPACE))
                    897:                p->flags &= ~TERMP_NOSPACE;
                    898:
                    899:        /*
1.42      kristaps  900:         * If ANSI (word-length styling), then apply our style now,
                    901:         * before the word.
1.25      kristaps  902:         */
                    903:
                    904:        for (i = 0; i < len; i++) {
                    905:                if ('\\' == word[i]) {
                    906:                        pescape(p, word, &i, len);
                    907:                        continue;
                    908:                }
1.42      kristaps  909:
1.45      kristaps  910:                if (TERMP_STYLE & p->flags) {
1.42      kristaps  911:                        if (TERMP_BOLD & p->flags) {
                    912:                                chara(p, word[i]);
                    913:                                chara(p, 8);
                    914:                        }
                    915:                        if (TERMP_UNDER & p->flags) {
                    916:                                chara(p, '_');
                    917:                                chara(p, 8);
                    918:                        }
                    919:                }
                    920:
1.25      kristaps  921:                chara(p, word[i]);
                    922:        }
                    923: }
                    924:
                    925:
                    926: /*
                    927:  * Like chara() but for arbitrary-length buffers.  Resize the buffer by
                    928:  * a factor of two (if the buffer is less than that) or the buffer's
                    929:  * size.
                    930:  */
                    931: static void
                    932: stringa(struct termp *p, const char *c, size_t sz)
                    933: {
                    934:        size_t           s;
                    935:
1.27      kristaps  936:        if (0 == sz)
                    937:                return;
                    938:
1.25      kristaps  939:        assert(c);
                    940:        if (p->col + sz >= p->maxcols) {
1.49    ! kristaps  941:                if (0 == p->maxcols)
        !           942:                        p->maxcols = 256;
        !           943:                s = sz > p->maxcols * 2 ? sz : p->maxcols * 2;
1.25      kristaps  944:                p->buf = realloc(p->buf, s);
                    945:                if (NULL == p->buf)
                    946:                        err(1, "realloc");
                    947:                p->maxcols = s;
                    948:        }
                    949:
1.49    ! kristaps  950:        (void)memcpy(&p->buf[(int)p->col], c, sz);
1.25      kristaps  951:        p->col += sz;
                    952: }
                    953:
                    954:
                    955: /*
                    956:  * Insert a single character into the line-buffer.  If the buffer's
                    957:  * space is exceeded, then allocate more space by doubling the buffer
                    958:  * size.
                    959:  */
                    960: static void
                    961: chara(struct termp *p, char c)
                    962: {
1.49    ! kristaps  963:        size_t           s;
1.25      kristaps  964:
                    965:        if (p->col + 1 >= p->maxcols) {
1.49    ! kristaps  966:                if (0 == p->maxcols)
        !           967:                        p->maxcols = 256;
        !           968:                s = p->maxcols * 2;
        !           969:                p->buf = realloc(p->buf, s);
1.25      kristaps  970:                if (NULL == p->buf)
1.49    ! kristaps  971:                        err(1, "realloc");
        !           972:                p->maxcols = s;
1.25      kristaps  973:        }
1.49    ! kristaps  974:        p->buf[(int)(p->col)++] = c;
1.25      kristaps  975: }
1.38      kristaps  976:
                    977:
                    978: static void
                    979: sanity(const struct mdoc_node *n)
                    980: {
                    981:
                    982:        switch (n->type) {
                    983:        case (MDOC_TEXT):
                    984:                if (n->child)
                    985:                        errx(1, "regular form violated (1)");
                    986:                if (NULL == n->parent)
                    987:                        errx(1, "regular form violated (2)");
                    988:                if (NULL == n->string)
                    989:                        errx(1, "regular form violated (3)");
                    990:                switch (n->parent->type) {
                    991:                case (MDOC_TEXT):
                    992:                        /* FALLTHROUGH */
                    993:                case (MDOC_ROOT):
                    994:                        errx(1, "regular form violated (4)");
                    995:                        /* NOTREACHED */
                    996:                default:
                    997:                        break;
                    998:                }
                    999:                break;
                   1000:        case (MDOC_ELEM):
                   1001:                if (NULL == n->parent)
                   1002:                        errx(1, "regular form violated (5)");
                   1003:                switch (n->parent->type) {
                   1004:                case (MDOC_TAIL):
                   1005:                        /* FALLTHROUGH */
                   1006:                case (MDOC_BODY):
                   1007:                        /* FALLTHROUGH */
                   1008:                case (MDOC_HEAD):
                   1009:                        break;
                   1010:                default:
                   1011:                        errx(1, "regular form violated (6)");
                   1012:                        /* NOTREACHED */
                   1013:                }
                   1014:                if (n->child) switch (n->child->type) {
                   1015:                case (MDOC_TEXT):
                   1016:                        break;
                   1017:                default:
                   1018:                        errx(1, "regular form violated (7(");
                   1019:                        /* NOTREACHED */
                   1020:                }
                   1021:                break;
                   1022:        case (MDOC_HEAD):
                   1023:                /* FALLTHROUGH */
                   1024:        case (MDOC_BODY):
                   1025:                /* FALLTHROUGH */
                   1026:        case (MDOC_TAIL):
                   1027:                if (NULL == n->parent)
                   1028:                        errx(1, "regular form violated (8)");
                   1029:                if (MDOC_BLOCK != n->parent->type)
                   1030:                        errx(1, "regular form violated (9)");
                   1031:                if (n->child) switch (n->child->type) {
                   1032:                case (MDOC_BLOCK):
                   1033:                        /* FALLTHROUGH */
                   1034:                case (MDOC_ELEM):
                   1035:                        /* FALLTHROUGH */
                   1036:                case (MDOC_TEXT):
                   1037:                        break;
                   1038:                default:
                   1039:                        errx(1, "regular form violated (a)");
                   1040:                        /* NOTREACHED */
                   1041:                }
                   1042:                break;
                   1043:        case (MDOC_BLOCK):
                   1044:                if (NULL == n->parent)
                   1045:                        errx(1, "regular form violated (b)");
                   1046:                if (NULL == n->child)
                   1047:                        errx(1, "regular form violated (c)");
                   1048:                switch (n->parent->type) {
                   1049:                case (MDOC_ROOT):
                   1050:                        /* FALLTHROUGH */
                   1051:                case (MDOC_HEAD):
                   1052:                        /* FALLTHROUGH */
                   1053:                case (MDOC_BODY):
                   1054:                        /* FALLTHROUGH */
                   1055:                case (MDOC_TAIL):
                   1056:                        break;
                   1057:                default:
                   1058:                        errx(1, "regular form violated (d)");
                   1059:                        /* NOTREACHED */
                   1060:                }
                   1061:                switch (n->child->type) {
                   1062:                case (MDOC_ROOT):
                   1063:                        /* FALLTHROUGH */
                   1064:                case (MDOC_ELEM):
                   1065:                        errx(1, "regular form violated (e)");
                   1066:                        /* NOTREACHED */
                   1067:                default:
                   1068:                        break;
                   1069:                }
                   1070:                break;
                   1071:        case (MDOC_ROOT):
                   1072:                if (n->parent)
                   1073:                        errx(1, "regular form violated (f)");
                   1074:                if (NULL == n->child)
                   1075:                        errx(1, "regular form violated (10)");
                   1076:                switch (n->child->type) {
                   1077:                case (MDOC_BLOCK):
                   1078:                        break;
                   1079:                default:
                   1080:                        errx(1, "regular form violated (11)");
                   1081:                        /* NOTREACHED */
                   1082:                }
                   1083:                break;
                   1084:        }
                   1085: }
                   1086:
1.43      kristaps 1087:
1.49    ! kristaps 1088: static int
        !          1089: merr(void *arg, int line, int col, const char *msg)
1.43      kristaps 1090: {
                   1091:
1.49    ! kristaps 1092:        warnx("error: %s (line %d, column %d)", msg, line, col);
        !          1093:        return(0);
        !          1094: }
1.43      kristaps 1095:
                   1096:
1.49    ! kristaps 1097: static int
        !          1098: mwarn(void *arg, int line, int col,
        !          1099:                enum mdoc_warn type, const char *msg)
        !          1100: {
        !          1101:        int              flags;
        !          1102:        char            *wtype;
1.43      kristaps 1103:
1.49    ! kristaps 1104:        flags = *(int *)arg;
        !          1105:        wtype = NULL;
1.43      kristaps 1106:
1.49    ! kristaps 1107:        switch (type) {
        !          1108:        case (WARN_COMPAT):
        !          1109:                wtype = "compat";
        !          1110:                if (flags & WARN_WCOMPAT)
        !          1111:                        break;
        !          1112:                return(1);
        !          1113:        case (WARN_SYNTAX):
        !          1114:                wtype = "syntax";
        !          1115:                if (flags & WARN_WSYNTAX)
        !          1116:                        break;
        !          1117:                return(1);
        !          1118:        }
1.43      kristaps 1119:
1.49    ! kristaps 1120:        assert(wtype);
        !          1121:        warnx("%s warning: %s (line %d, column %d)",
        !          1122:                        wtype, msg, line, col);
        !          1123:
        !          1124:        if ( ! (flags & WARN_WERR))
        !          1125:                return(1);
        !          1126:
        !          1127:        warnx("%s: considering warnings as errors",
        !          1128:                        __progname);
        !          1129:        return(0);
1.43      kristaps 1130: }
                   1131:
1.49    ! kristaps 1132:

CVSweb