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

Annotation of texi2mdoc/main.c, Revision 1.3

1.3     ! kristaps    1: /*     $Id: main.c,v 1.2 2015/02/17 10:27:18 kristaps Exp $ */
1.1       kristaps    2: /*
                      3:  * Copyright (c) 2015 Kristaps Dzonsons <kristaps@bsd.lv>
                      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 above
                      7:  * copyright notice and this permission notice appear in all copies.
                      8:  *
                      9:  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
                     10:  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
                     11:  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
                     12:  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
                     13:  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
                     14:  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
                     15:  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
                     16:  */
                     17: #include <sys/mman.h>
                     18: #include <sys/stat.h>
                     19:
                     20: #include <assert.h>
                     21: #include <ctype.h>
                     22: #include <fcntl.h>
                     23: #include <getopt.h>
1.2       kristaps   24: #include <libgen.h>
                     25: #include <limits.h>
1.1       kristaps   26: #include <stdarg.h>
                     27: #include <stdio.h>
                     28: #include <stdlib.h>
                     29: #include <string.h>
                     30:
                     31: /*
                     32:  * This defines each one of the Texinfo commands that we understand.
                     33:  * Obviously this only refers to native commands; overriden names are a
                     34:  * different story.
                     35:  */
                     36: enum   texicmd {
1.2       kristaps   37:        TEXICMD_ACRONYM,
1.1       kristaps   38:        TEXICMD_A4PAPER,
                     39:        TEXICMD_ANCHOR,
1.2       kristaps   40:        TEXICMD_APPENDIX,
                     41:        TEXICMD_APPENDIXSEC,
1.3     ! kristaps   42:        TEXICMD_ASTERISK,
1.1       kristaps   43:        TEXICMD_AT,
1.3     ! kristaps   44:        TEXICMD_AUTHOR,
        !            45:        TEXICMD_BANG,
1.1       kristaps   46:        TEXICMD_BYE,
                     47:        TEXICMD_CHAPTER,
                     48:        TEXICMD_CINDEX,
1.3     ! kristaps   49:        TEXICMD_CITE,
1.1       kristaps   50:        TEXICMD_CODE,
1.3     ! kristaps   51:        TEXICMD_COLON,
1.1       kristaps   52:        TEXICMD_COMMAND,
                     53:        TEXICMD_COMMENT,
1.2       kristaps   54:        TEXICMD_COMMENT_LONG,
1.1       kristaps   55:        TEXICMD_CONTENTS,
                     56:        TEXICMD_COPYING,
                     57:        TEXICMD_COPYRIGHT,
1.3     ! kristaps   58:        TEXICMD_DEFTYPEFN,
        !            59:        TEXICMD_DEFTYPEFNX,
        !            60:        TEXICMD_DEFTYPEFUN,
        !            61:        TEXICMD_DEFTYPEFUNX,
        !            62:        TEXICMD_DEFTYPEVAR,
        !            63:        TEXICMD_DEFTYPEVR,
1.1       kristaps   64:        TEXICMD_DETAILMENU,
1.3     ! kristaps   65:        TEXICMD_DFN,
1.1       kristaps   66:        TEXICMD_DIRCATEGORY,
                     67:        TEXICMD_DIRENTRY,
1.3     ! kristaps   68:        TEXICMD_DISPLAY,
1.2       kristaps   69:        TEXICMD_DOTS,
1.1       kristaps   70:        TEXICMD_EMAIL,
                     71:        TEXICMD_EMPH,
                     72:        TEXICMD_END,
1.2       kristaps   73:        TEXICMD_ENUMERATE,
1.3     ! kristaps   74:        TEXICMD_ENV,
1.1       kristaps   75:        TEXICMD_EXAMPLE,
                     76:        TEXICMD_FILE,
1.3     ! kristaps   77:        TEXICMD_GROUP,
1.2       kristaps   78:        TEXICMD_HEADING,
1.3     ! kristaps   79:        TEXICMD_HEADINGS,
        !            80:        TEXICMD_HYPHEN,
1.1       kristaps   81:        TEXICMD_I,
1.3     ! kristaps   82:        TEXICMD_IFCLEAR,
1.1       kristaps   83:        TEXICMD_IFHTML,
1.3     ! kristaps   84:        TEXICMD_IFINFO,
1.1       kristaps   85:        TEXICMD_IFNOTTEX,
                     86:        TEXICMD_IFTEX,
1.3     ! kristaps   87:        TEXICMD_IFSET,
1.1       kristaps   88:        TEXICMD_IMAGE,
1.2       kristaps   89:        TEXICMD_INCLUDE,
1.1       kristaps   90:        TEXICMD_ITEM,
                     91:        TEXICMD_ITEMIZE,
                     92:        TEXICMD_KBD,
                     93:        TEXICMD_LATEX,
1.3     ! kristaps   94:        TEXICMD_MATH,
1.1       kristaps   95:        TEXICMD_MENU,
1.3     ! kristaps   96:        TEXICMD_NEWLINE,
1.1       kristaps   97:        TEXICMD_NODE,
1.3     ! kristaps   98:        TEXICMD_NOINDENT,
        !            99:        TEXICMD_QUESTIONMARK,
1.1       kristaps  100:        TEXICMD_QUOTATION,
1.3     ! kristaps  101:        TEXICMD_PAGE,
1.1       kristaps  102:        TEXICMD_PARINDENT,
1.2       kristaps  103:        TEXICMD_PRINTINDEX,
1.1       kristaps  104:        TEXICMD_REF,
                    105:        TEXICMD_SAMP,
                    106:        TEXICMD_SECTION,
1.3     ! kristaps  107:        TEXICMD_SET,
1.1       kristaps  108:        TEXICMD_SETCHAPNEWPAGE,
                    109:        TEXICMD_SETFILENAME,
                    110:        TEXICMD_SETTITLE,
1.3     ! kristaps  111:        TEXICMD_SP,
        !           112:        TEXICMD_SPACE,
        !           113:        TEXICMD_SMALLEXAMPLE,
        !           114:        TEXICMD_SQUIGGLE_LEFT,
        !           115:        TEXICMD_SQUIGGLE_RIGHT,
1.1       kristaps  116:        TEXICMD_SUBSECTION,
1.3     ! kristaps  117:        TEXICMD_SUBTITLE,
        !           118:        TEXICMD_TAB,
1.1       kristaps  119:        TEXICMD_TABLE,
                    120:        TEXICMD_TEX,
                    121:        TEXICMD_TEXSYM,
1.3     ! kristaps  122:        TEXICMD_TITLE,
1.1       kristaps  123:        TEXICMD_TITLEFONT,
                    124:        TEXICMD_TITLEPAGE,
                    125:        TEXICMD_TOP,
                    126:        TEXICMD_UNNUMBERED,
1.2       kristaps  127:        TEXICMD_UNNUMBEREDSEC,
1.3     ! kristaps  128:        TEXICMD_UREF,
1.1       kristaps  129:        TEXICMD_URL,
                    130:        TEXICMD_VAR,
1.3     ! kristaps  131:        TEXICMD_W,
1.1       kristaps  132:        TEXICMD__MAX
                    133: };
                    134:
                    135: /*
                    136:  * The file currently being parsed.
                    137:  * This keeps track of our location within that file.
                    138:  */
                    139: struct texifile {
                    140:        const char      *name; /* name of the file */
                    141:        size_t           line; /* current line (from zero) */
                    142:        size_t           col; /* current column in line (from zero) */
                    143:        char            *map; /* mmap'd file */
                    144:        size_t           mapsz; /* size of mmap */
                    145: };
                    146:
                    147: struct texi;
                    148:
1.2       kristaps  149: /*
                    150:  * Callback for functions implementing texi commands.
                    151:  */
1.1       kristaps  152: typedef        void (*texicmdfp)(struct texi *,
                    153:        enum texicmd, const char *, size_t, size_t *);
                    154:
                    155: /*
                    156:  * Describes Texinfo commands, whether native or overriden.
                    157:  */
                    158: struct texitok {
                    159:        texicmdfp        fp; /* callback (or NULL if none) */
                    160:        const char      *tok; /* name of the token */
                    161:        size_t           len; /* strlen(tok) */
                    162: };
                    163:
1.3     ! kristaps  164: enum   texilist {
        !           165:        TEXILIST_NONE = 0,
        !           166:        TEXILIST_ITEM,
        !           167:        TEXILIST_NOITEM,
        !           168: };
        !           169:
1.1       kristaps  170: /*
                    171:  * The main parse structure.
                    172:  * This keeps any necessary information handy.
                    173:  */
                    174: struct texi {
                    175:        struct texifile  files[64];
                    176:        size_t           filepos;
                    177:        size_t           outcol; /* column of output */
                    178:        int              outmacro; /* whether output is in line macro */
                    179:        int              seenws; /* whitespace has been ignored */
1.3     ! kristaps  180:        int              ign; /* don't print anything */
        !           181:        int              literal;
1.2       kristaps  182:        char            *dir; /* texi directory */
1.3     ! kristaps  183:        enum texilist    list;
1.1       kristaps  184: };
                    185:
1.2       kristaps  186: /* FIXME: don't use this crap. */
1.1       kristaps  187: #define        ismpunct(_x) \
                    188:        ('.' == (_x) || \
                    189:         ',' == (_x) || \
                    190:         ';' == (_x))
1.2       kristaps  191: #define        isws(_x) \
                    192:        (' ' == (_x) || '\t' == (_x))
1.1       kristaps  193:
                    194: static void doarg1(struct texi *, enum texicmd, const char *, size_t, size_t *);
1.3     ! kristaps  195: static void doblock(struct texi *, enum texicmd, const char *, size_t, size_t *);
1.1       kristaps  196: static void dobracket(struct texi *, enum texicmd, const char *, size_t, size_t *);
                    197: static void dobye(struct texi *, enum texicmd, const char *, size_t, size_t *);
1.3     ! kristaps  198: static void dochapter(struct texi *, enum texicmd, const char *, size_t, size_t *);
1.1       kristaps  199: static void docommand(struct texi *, enum texicmd, const char *, size_t, size_t *);
1.3     ! kristaps  200: static void dodeftypefun(struct texi *, enum texicmd, const char *, size_t, size_t *);
        !           201: static void dodeftypevar(struct texi *, enum texicmd, const char *, size_t, size_t *);
        !           202: static void dodisplay(struct texi *, enum texicmd, const char *, size_t, size_t *);
1.1       kristaps  203: static void doemph(struct texi *, enum texicmd, const char *, size_t, size_t *);
1.2       kristaps  204: static void doenumerate(struct texi *, enum texicmd, const char *, size_t, size_t *);
1.3     ! kristaps  205: static void doenv(struct texi *, enum texicmd, const char *, size_t, size_t *);
1.1       kristaps  206: static void doexample(struct texi *, enum texicmd, const char *, size_t, size_t *);
                    207: static void dofile(struct texi *, enum texicmd, const char *, size_t, size_t *);
                    208: static void doignblock(struct texi *, enum texicmd, const char *, size_t, size_t *);
                    209: static void doignbracket(struct texi *, enum texicmd, const char *, size_t, size_t *);
                    210: static void doignline(struct texi *, enum texicmd, const char *, size_t, size_t *);
1.2       kristaps  211: static void doinclude(struct texi *, enum texicmd, const char *, size_t, size_t *);
1.1       kristaps  212: static void doitalic(struct texi *, enum texicmd, const char *, size_t, size_t *);
                    213: static void doitem(struct texi *, enum texicmd, const char *, size_t, size_t *);
                    214: static void doitemize(struct texi *, enum texicmd, const char *, size_t, size_t *);
                    215: static void doliteral(struct texi *, enum texicmd, const char *, size_t, size_t *);
1.3     ! kristaps  216: static void domath(struct texi *, enum texicmd, const char *, size_t, size_t *);
1.1       kristaps  217: static void doquotation(struct texi *, enum texicmd, const char *, size_t, size_t *);
                    218: static void dotable(struct texi *, enum texicmd, const char *, size_t, size_t *);
                    219: static void dotop(struct texi *, enum texicmd, const char *, size_t, size_t *);
                    220: static void dosection(struct texi *, enum texicmd, const char *, size_t, size_t *);
1.3     ! kristaps  221: static void dosp(struct texi *, enum texicmd, const char *, size_t, size_t *);
1.1       kristaps  222: static void dosubsection(struct texi *, enum texicmd, const char *, size_t, size_t *);
                    223: static void dosymbol(struct texi *, enum texicmd, const char *, size_t, size_t *);
                    224:
                    225: static const struct texitok texitoks[TEXICMD__MAX] = {
1.2       kristaps  226:        { doarg1, "acronym", 7 }, /* TEXICMD_ACRONYM */
1.1       kristaps  227:        { doignline, "afourpaper", 10 }, /* TEXICMD_A4PAPER */
                    228:        { doignbracket, "anchor", 6 }, /* TEXICMD_ANCHOR */
1.3     ! kristaps  229:        { dochapter, "appendix", 8 }, /* TEXICMD_APPENDIX */
        !           230:        { dochapter, "appendixsec", 11 }, /* TEXICMD_APPENDIXSEC */
        !           231:        { dosymbol, "*", 1 }, /* TEXICMD_ASTERISK */
1.1       kristaps  232:        { dosymbol, "@", 1 }, /* TEXICMD_AT */
1.3     ! kristaps  233:        { doignline, "author", 6 }, /* TEXICMD_AUTHOR */
        !           234:        { dosymbol, "!", 1 }, /* TEXICMD_BANG */
1.1       kristaps  235:        { dobye, "bye", 3 }, /* TEXICMD_BYE */
1.3     ! kristaps  236:        { dochapter, "chapter", 7 }, /* TEXICMD_CHAPTER */
1.1       kristaps  237:        { doignline, "cindex", 6 }, /* TEXICMD_CINDEX */
                    238:        { doliteral, "code", 4 }, /* TEXICMD_CODE */
1.3     ! kristaps  239:        { doitalic, "cite", 4 }, /* TEXICMD_CITE */
        !           240:        { dosymbol, ":", 1 }, /* TEXICMD_COLON */
1.1       kristaps  241:        { docommand, "command", 7 }, /* TEXICMD_COMMAND */
                    242:        { doignline, "c", 1 }, /* TEXICMD_COMMENT */
1.2       kristaps  243:        { doignline, "comment", 7 }, /* TEXICMD_COMMENT_LONG */
1.1       kristaps  244:        { doignline, "contents", 8 }, /* TEXICMD_CONTENTS */
                    245:        { doignblock, "copying", 7 }, /* TEXICMD_COPYING */
                    246:        { dosymbol, "copyright", 9 }, /* TEXICMD_COPYRIGHT */
1.3     ! kristaps  247:        { dodeftypefun, "deftypefn", 9 }, /* TEXICMD_DEFTYPEFN */
        !           248:        { dodeftypefun, "deftypefnx", 10 }, /* TEXICMD_DEFTYPEFNX */
        !           249:        { dodeftypefun, "deftypefun", 10 }, /* TEXICMD_DEFTYPEFUN */
        !           250:        { dodeftypefun, "deftypefunx", 11 }, /* TEXICMD_DEFTYPEFUNX */
        !           251:        { dodeftypevar, "deftypevar", 10 }, /* TEXICMD_DEFTYPEVAR */
        !           252:        { dodeftypevar, "deftypevr", 9 }, /* TEXICMD_DEFTYPEVR */
1.1       kristaps  253:        { doignblock, "detailmenu", 10 }, /* TEXICMD_DETAILMENU */
1.3     ! kristaps  254:        { doitalic, "dfn", 3 }, /* TEXICMD_DFN */
1.1       kristaps  255:        { doignline, "dircategory", 11 }, /* TEXICMD_DIRCATEGORY */
                    256:        { doignblock, "direntry", 8 }, /* TEXICMD_DIRENTRY */
1.3     ! kristaps  257:        { dodisplay, "display", 7 }, /* TEXICMD_DISPLAY */
1.2       kristaps  258:        { dosymbol, "dots", 4 }, /* TEXICMD_DOTS */
1.1       kristaps  259:        { doarg1, "email", 5 }, /* TEXICMD_EMAIL */
                    260:        { doemph, "emph", 4 }, /* TEXICMD_EMPH */
                    261:        { NULL, "end", 3 }, /* TEXICMD_END */
1.2       kristaps  262:        { doenumerate, "enumerate", 9 }, /* TEXICMD_ENUMERATE */
1.3     ! kristaps  263:        { doenv, "env", 3 }, /* TEXICMD_ENV */
1.1       kristaps  264:        { doexample, "example", 7 }, /* TEXICMD_EXAMPLE */
                    265:        { dofile, "file", 4 }, /* TEXICMD_FILE */
1.3     ! kristaps  266:        { doblock, "group", 5 }, /* TEXICMD_GROUP */
1.2       kristaps  267:        { dosection, "heading", 7 }, /* TEXICMD_HEADING */
1.3     ! kristaps  268:        { doignline, "headings", 8 }, /* TEXICMD_HEADINGS */
        !           269:        { dosymbol, "-", 1 }, /* TEXICMD_HYPHEN */
1.1       kristaps  270:        { doitalic, "i", 1 }, /* TEXICMD_I */
1.3     ! kristaps  271:        { doignblock, "ifclear", 7 }, /* TEXICMD_IFCLEAR */
1.1       kristaps  272:        { doignblock, "ifhtml", 6 }, /* TEXICMD_IFHTML */
1.3     ! kristaps  273:        { doignblock, "ifinfo", 6 }, /* TEXICMD_IFINFO */
        !           274:        { doblock, "ifnottex", 8 }, /* TEXICMD_IFNOTTEX */
1.1       kristaps  275:        { doignblock, "iftex", 5 }, /* TEXICMD_IFTEX */
1.3     ! kristaps  276:        { doignblock, "ifset", 5 }, /* TEXICMD_IFSET */
1.1       kristaps  277:        { doignbracket, "image", 5 }, /* TEXICMD_IMAGE */
1.2       kristaps  278:        { doinclude, "include", 7 }, /* TEXICMD_INCLUDE */
1.1       kristaps  279:        { doitem, "item", 4 }, /* TEXICMD_ITEM */
                    280:        { doitemize, "itemize", 7 }, /* TEXICMD_ITEMIZE */
                    281:        { doliteral, "kbd", 3 }, /* TEXICMD_KBD */
                    282:        { dosymbol, "LaTeX", 5 }, /* TEXICMD_LATEX */
1.3     ! kristaps  283:        { domath, "math", 4 }, /* TEXICMD_MATH */
1.1       kristaps  284:        { doignblock, "menu", 4 }, /* TEXICMD_MENU */
1.3     ! kristaps  285:        { dosymbol, "\n", 1 }, /* TEXICMD_NEWLINE */
1.1       kristaps  286:        { doignline, "node", 4 }, /* TEXICMD_NODE */
1.3     ! kristaps  287:        { doignline, "noindent", 8 }, /* TEXICMD_NOINDENT */
        !           288:        { dosymbol, "?", 1 }, /* TEXICMD_QUESTIONMARK */
1.1       kristaps  289:        { doquotation, "quotation", 9 }, /* TEXICMD_QUOTATION */
1.3     ! kristaps  290:        { doignline, "page", 4 }, /* TEXICMD_PAGE */
        !           291:        { doignline, "paragraphindent", 14 }, /* TEXICMD_PARINDENT */
1.2       kristaps  292:        { doignline, "printindex", 10 }, /* TEXICMD_PRINTINDEX */
1.1       kristaps  293:        { dobracket, "ref", 3 }, /* TEXICMD_REF */
                    294:        { doliteral, "samp", 4 }, /* TEXICMD_SAMP */
                    295:        { dosection, "section", 7 }, /* TEXICMD_SECTION */
1.3     ! kristaps  296:        { doignline, "set", 3 }, /* TEXICMD_SET */
1.1       kristaps  297:        { doignline, "setchapternewpage", 17 }, /* TEXICMD_SETCHAPNEWPAGE */
                    298:        { doignline, "setfilename", 11 }, /* TEXICMD_SETFILENAME */
1.3     ! kristaps  299:        { dosp, "sp", 2 }, /* TEXICMD_SP */
        !           300:        { dosymbol, " ", 1 }, /* TEXICMD_SPACE */
        !           301:        { doexample, "smallexample", 12 }, /* TEXICMD_SMALLEXAMPLE */
1.1       kristaps  302:        { doignline, "settitle", 8 }, /* TEXICMD_SETTITLE */
1.3     ! kristaps  303:        { dosymbol, "{", 1 }, /* TEXICMD_SQUIGGLE_LEFT */
        !           304:        { dosymbol, "}", 1 }, /* TEXICMD_SQUIGGLE_RIGHT */
1.1       kristaps  305:        { dosubsection, "subsection", 10 }, /* TEXICMD_SUBSECTION */
1.3     ! kristaps  306:        { doignline, "subtitle", 8 }, /* TEXICMD_SUBTITLE */
        !           307:        { dosymbol, "\t", 1 }, /* TEXICMD_TAB */
1.1       kristaps  308:        { dotable, "table", 5 }, /* TEXICMD_TABLE */
                    309:        { doignblock, "tex", 3 }, /* TEXICMD_TEX */
                    310:        { dosymbol, "TeX", 3 }, /* TEXICMD_TEXSYM */
1.3     ! kristaps  311:        { doignline, "title", 5 }, /* TEXICMD_TITLE */
1.1       kristaps  312:        { dobracket, "titlefont", 9 }, /* TEXICMD_TITLEFONT */
                    313:        { doignblock, "titlepage", 9 }, /* TEXICMD_TITLEPAGE */
                    314:        { dotop, "top", 3 }, /* TEXICMD_TOP */
1.3     ! kristaps  315:        { dochapter, "unnumbered", 10 }, /* TEXICMD_UNNUMBERED */
1.2       kristaps  316:        { dosection, "unnumberedsec", 13 }, /* TEXICMD_UNNUMBEREDSEC */
1.3     ! kristaps  317:        { doarg1, "uref", 4 }, /* TEXICMD_UREF */
1.1       kristaps  318:        { doarg1, "url", 3 }, /* TEXICMD_URL */
                    319:        { doliteral, "var", 3 }, /* TEXICMD_VAR */
1.3     ! kristaps  320:        { dobracket, "w", 1 }, /* TEXICMD_W */
1.1       kristaps  321: };
                    322:
1.2       kristaps  323: /*
                    324:  * Unmap the top-most file that we're using.
                    325:  */
1.1       kristaps  326: static void
                    327: texifilepop(struct texi *p)
                    328: {
                    329:        struct texifile *f;
                    330:
                    331:        assert(p->filepos > 0);
                    332:        f = &p->files[--p->filepos];
                    333:        munmap(f->map, f->mapsz);
                    334: }
                    335:
1.2       kristaps  336: /*
                    337:  * Unmap all files that we're currently using.
                    338:  * The utility should exit(...) after this is called.
                    339:  */
1.1       kristaps  340: static void
                    341: texiexit(struct texi *p)
                    342: {
                    343:
                    344:        while (p->filepos > 0)
                    345:                texifilepop(p);
1.2       kristaps  346:        free(p->dir);
1.3     ! kristaps  347:        if (p->outcol)
        !           348:                putchar('\n');
1.1       kristaps  349: }
                    350:
1.2       kristaps  351: /*
                    352:  * Fatal error: unmap all files and exit.
                    353:  * The "errstring" is passed to perror(3).
                    354:  */
1.1       kristaps  355: static void
1.2       kristaps  356: texiabort(struct texi *p, const char *errstring)
1.1       kristaps  357: {
                    358:
                    359:        perror(errstring);
                    360:        texiexit(p);
                    361:        exit(EXIT_FAILURE);
                    362: }
                    363:
                    364: /*
                    365:  * Print a generic warning message (to stderr) tied to our current
                    366:  * location in the parse sequence.
                    367:  */
                    368: static void
                    369: texiwarn(const struct texi *p, const char *fmt, ...)
                    370: {
                    371:        va_list  ap;
                    372:
1.2       kristaps  373:        fprintf(stderr, "%s:%zu:%zu: warning: ",
1.1       kristaps  374:                p->files[p->filepos - 1].name,
                    375:                p->files[p->filepos - 1].line + 1,
                    376:                p->files[p->filepos - 1].col + 1);
                    377:        va_start(ap, fmt);
                    378:        vfprintf(stderr, fmt, ap);
                    379:        va_end(ap);
                    380:        fputc('\n', stderr);
                    381: }
                    382:
1.2       kristaps  383: static void
                    384: texierr(struct texi *p, const char *fmt, ...)
                    385: {
                    386:        va_list  ap;
                    387:
                    388:        fprintf(stderr, "%s:%zu:%zu: error: ",
                    389:                p->files[p->filepos - 1].name,
                    390:                p->files[p->filepos - 1].line + 1,
                    391:                p->files[p->filepos - 1].col + 1);
                    392:        va_start(ap, fmt);
                    393:        vfprintf(stderr, fmt, ap);
                    394:        va_end(ap);
                    395:        fputc('\n', stderr);
                    396:        texiexit(p);
                    397:        exit(EXIT_FAILURE);
                    398: }
                    399:
1.1       kristaps  400: /*
                    401:  * Put a single data character.
                    402:  * This MUST NOT be a mdoc(7) command: it should be free text that's
                    403:  * outputted to the screen.
                    404:  */
                    405: static void
                    406: texiputchar(struct texi *p, char c)
                    407: {
                    408:
1.3     ! kristaps  409:        if (p->ign)
1.1       kristaps  410:                return;
                    411:        putchar(c);
                    412:        if ('\n' == c) {
                    413:                p->outcol = 0;
                    414:                p->seenws = 0;
                    415:        } else
                    416:                p->outcol++;
                    417: }
                    418:
                    419: /*
                    420:  * Put multiple characters (see texiputchar()).
                    421:  */
                    422: static void
                    423: texiputchars(struct texi *p, const char *s)
                    424: {
                    425:
                    426:        while ('\0' != *s)
                    427:                texiputchar(p, *s++);
                    428: }
                    429:
                    430: /*
                    431:  * Put an mdoc(7) command without the trailing newline.
                    432:  * This should ONLY be used for mdoc(7) commands!
                    433:  */
                    434: static void
1.3     ! kristaps  435: teximacroclose(struct texi *p)
        !           436: {
        !           437:
        !           438:        p->outmacro--;
        !           439:        if (p->ign)
        !           440:                return;
        !           441:        texiputchar(p, '\n');
        !           442: }
        !           443:
        !           444: /*
        !           445:  * Put an mdoc(7) command without the trailing newline.
        !           446:  * This should ONLY be used for mdoc(7) commands!
        !           447:  */
        !           448: static void
        !           449: teximacroopen(struct texi *p, const char *s)
1.1       kristaps  450: {
                    451:        int      rc;
                    452:
1.3     ! kristaps  453:        p->outmacro++;
        !           454:        if (p->ign)
1.1       kristaps  455:                return;
                    456:        if (p->outcol)
                    457:                texiputchar(p, '\n');
                    458:        if (EOF != (rc = fputs(s, stdout)))
                    459:                p->outcol += rc;
                    460: }
                    461:
                    462: /*
                    463:  * Put an mdoc(7) command with the trailing newline.
                    464:  * This should ONLY be used for mdoc(7) commands!
                    465:  */
                    466: static void
                    467: teximacro(struct texi *p, const char *s)
                    468: {
                    469:
1.3     ! kristaps  470:        if (p->ign)
1.1       kristaps  471:                return;
                    472:        if (p->outcol)
                    473:                texiputchar(p, '\n');
                    474:        puts(s);
                    475:        p->outcol = 0;
                    476:        p->seenws = 0;
                    477: }
                    478:
                    479: /*
                    480:  * Advance by a single byte in the input stream.
                    481:  */
                    482: static void
                    483: advance(struct texi *p, const char *buf, size_t *pos)
                    484: {
                    485:
                    486:        if ('\n' == buf[*pos]) {
                    487:                p->files[p->filepos - 1].line++;
                    488:                p->files[p->filepos - 1].col = 0;
                    489:        } else
                    490:                p->files[p->filepos - 1].col++;
                    491:
                    492:        (*pos)++;
                    493: }
                    494:
                    495: /*
                    496:  * Advance to the next non-whitespace word in the input stream.
                    497:  * If we're in literal mode, then print all of the whitespace as we're
                    498:  * doing so.
                    499:  */
                    500: static size_t
                    501: advancenext(struct texi *p, const char *buf, size_t sz, size_t *pos)
                    502: {
                    503:
1.3     ! kristaps  504:        if (p->literal) {
1.1       kristaps  505:                while (*pos < sz && isspace(buf[*pos])) {
                    506:                        texiputchar(p, buf[*pos]);
                    507:                        advance(p, buf, pos);
                    508:                }
                    509:                return(*pos);
                    510:        }
                    511:
                    512:        while (*pos < sz && isspace(buf[*pos])) {
                    513:                p->seenws = 1;
                    514:                /*
                    515:                 * If it looks like we've printed a double-line, then
                    516:                 * output a paragraph.
                    517:                 * FIXME: this is stupid.
                    518:                 */
                    519:                if (*pos && '\n' == buf[*pos] && '\n' == buf[*pos - 1])
                    520:                        teximacro(p, ".Pp");
                    521:                advance(p, buf, pos);
                    522:        }
                    523:        return(*pos);
                    524: }
                    525:
                    526: /*
                    527:  * Advance to the EOLN in the input stream.
                    528:  */
                    529: static size_t
1.3     ! kristaps  530: advanceeoln(struct texi *p, const char *buf,
        !           531:        size_t sz, size_t *pos, int consumenl)
1.1       kristaps  532: {
                    533:
                    534:        while (*pos < sz && '\n' != buf[*pos])
                    535:                advance(p, buf, pos);
1.3     ! kristaps  536:        if (*pos < sz && consumenl)
        !           537:                advance(p, buf, pos);
1.1       kristaps  538:        return(*pos);
                    539: }
                    540:
                    541: /*
                    542:  * Advance to position "end", which is an absolute position in the
                    543:  * current buffer greater than or equal to the current position.
                    544:  */
                    545: static void
                    546: advanceto(struct texi *p, const char *buf, size_t *pos, size_t end)
                    547: {
                    548:
                    549:        assert(*pos <= end);
                    550:        while (*pos < end)
                    551:                advance(p, buf, pos);
                    552: }
                    553:
                    554: /*
                    555:  * Output a free-form word in the input stream, progressing to the next
                    556:  * command or white-space.
                    557:  * This also will advance the input stream.
                    558:  */
                    559: static void
                    560: texiword(struct texi *p, const char *buf, size_t sz, size_t *pos)
                    561: {
                    562:
                    563:        /*
                    564:         * XXX: if we're in literal mode, then we shouldn't do any
                    565:         * reflowing of text here.
                    566:         */
1.3     ! kristaps  567:        if (0 == p->outmacro && p->outcol > 72 && 0 == p->literal)
1.1       kristaps  568:                texiputchar(p, '\n');
                    569:
1.3     ! kristaps  570:        if (p->seenws && p->outcol && 0 == p->literal)
1.1       kristaps  571:                texiputchar(p, ' ');
                    572:
                    573:        p->seenws = 0;
                    574:
                    575:        while (*pos < sz && ! isspace(buf[*pos])) {
                    576:                switch (buf[*pos]) {
                    577:                case ('@'):
                    578:                case ('}'):
                    579:                case ('{'):
                    580:                        return;
                    581:                }
                    582:                if (*pos < sz - 1 &&
                    583:                         '`' == buf[*pos] &&
                    584:                         '`' == buf[*pos + 1]) {
                    585:                        texiputchars(p, "\\(lq");
                    586:                        advance(p, buf, pos);
                    587:                } else if (*pos < sz - 1 &&
                    588:                         '\'' == buf[*pos] &&
                    589:                         '\'' == buf[*pos + 1]) {
                    590:                        texiputchars(p, "\\(rq");
                    591:                        advance(p, buf, pos);
                    592:                } else
                    593:                        texiputchar(p, buf[*pos]);
                    594:                advance(p, buf, pos);
                    595:        }
                    596: }
                    597:
                    598: static enum texicmd
                    599: texicmd(struct texi *p, const char *buf,
                    600:        size_t pos, size_t sz, size_t *end)
                    601: {
                    602:        size_t   i, len;
                    603:
                    604:        assert('@' == buf[pos]);
1.3     ! kristaps  605:
        !           606:        if (++pos >= sz)
        !           607:                return(TEXICMD__MAX);
        !           608:
        !           609:        /* Alphabetic commands are special. */
        !           610:        if ( ! isalpha(buf[pos])) {
        !           611:                *end = pos + 1;
        !           612:                for (i = 0; i < TEXICMD__MAX; i++) {
        !           613:                        if (1 != texitoks[i].len)
        !           614:                                continue;
        !           615:                        if (0 == strncmp(texitoks[i].tok, &buf[pos], 1))
        !           616:                                return(i);
        !           617:                }
        !           618:                texiwarn(p, "bad command: @%c", buf[pos]);
        !           619:                return(TEXICMD__MAX);
        !           620:        }
        !           621:
        !           622:        for (*end = pos; *end < sz && ! isspace(buf[*end]); (*end)++)
        !           623:                if ((*end > pos && ('@' == buf[*end] ||
        !           624:                          '{' == buf[*end] || '}' == buf[*end])))
1.1       kristaps  625:                        break;
                    626:
                    627:        len = *end - pos;
                    628:        for (i = 0; i < TEXICMD__MAX; i++) {
                    629:                if (len != texitoks[i].len)
                    630:                        continue;
                    631:                if (0 == strncmp(texitoks[i].tok, &buf[pos], len))
                    632:                        return(i);
                    633:        }
                    634:
1.3     ! kristaps  635:        texiwarn(p, "bad command: @%.*s", (int)len, &buf[pos]);
1.1       kristaps  636:        return(TEXICMD__MAX);
                    637: }
                    638:
                    639: static void
                    640: parseeof(struct texi *p, const char *buf, size_t sz)
                    641: {
                    642:        size_t           pos = 0;
                    643:        enum texicmd     cmd;
                    644:        size_t           end;
                    645:
                    646:        while ((pos = advancenext(p, buf, sz, &pos)) < sz) {
                    647:                switch (buf[pos]) {
                    648:                case ('}'):
1.3     ! kristaps  649:                        if (0 == p->ign)
        !           650:                                texiwarn(p, "unexpected \"}\"");
1.1       kristaps  651:                        advance(p, buf, &pos);
                    652:                        continue;
                    653:                case ('{'):
1.3     ! kristaps  654:                        if (0 == p->ign)
        !           655:                                texiwarn(p, "unexpected \"{\"");
1.1       kristaps  656:                        advance(p, buf, &pos);
                    657:                        continue;
                    658:                case ('@'):
                    659:                        break;
                    660:                default:
                    661:                        texiword(p, buf, sz, &pos);
                    662:                        continue;
                    663:                }
                    664:
                    665:                cmd = texicmd(p, buf, pos, sz, &end);
                    666:                advanceto(p, buf, &pos, end);
                    667:                if (TEXICMD__MAX == cmd)
                    668:                        continue;
                    669:                if (NULL != texitoks[cmd].fp)
                    670:                        (*texitoks[cmd].fp)(p, cmd, buf, sz, &pos);
                    671:        }
                    672: }
                    673:
                    674: static void
                    675: parsebracket(struct texi *p, const char *buf, size_t sz, size_t *pos)
                    676: {
                    677:        size_t           end;
                    678:        enum texicmd     cmd;
                    679:
1.3     ! kristaps  680:        while (*pos < sz && isspace(buf[*pos]))
        !           681:                advance(p, buf, pos);
        !           682:
1.1       kristaps  683:        if (*pos == sz || '{' != buf[*pos])
                    684:                return;
                    685:        advance(p, buf, pos);
                    686:
                    687:        while ((*pos = advancenext(p, buf, sz, pos)) < sz) {
                    688:                switch (buf[*pos]) {
                    689:                case ('}'):
                    690:                        advance(p, buf, pos);
                    691:                        return;
                    692:                case ('{'):
1.3     ! kristaps  693:                        if (0 == p->ign)
        !           694:                                texiwarn(p, "unexpected \"{\"");
        !           695:                        advance(p, buf, pos);
        !           696:                        continue;
        !           697:                case ('@'):
        !           698:                        break;
        !           699:                default:
        !           700:                        texiword(p, buf, sz, pos);
        !           701:                        continue;
        !           702:                }
        !           703:
        !           704:                cmd = texicmd(p, buf, *pos, sz, &end);
        !           705:                advanceto(p, buf, pos, end);
        !           706:                if (TEXICMD__MAX == cmd)
        !           707:                        continue;
        !           708:                if (NULL != texitoks[cmd].fp)
        !           709:                        (*texitoks[cmd].fp)(p, cmd, buf, sz, pos);
        !           710:        }
        !           711: }
        !           712:
        !           713: /*
        !           714:  * This should be invoked when we're on a macro line and want to process
        !           715:  * to the end of the current input line, doing all of our macros along
        !           716:  * the way.
        !           717:  */
        !           718: static void
        !           719: parseeoln(struct texi *p, const char *buf, size_t sz, size_t *pos)
        !           720: {
        !           721:        size_t           end;
        !           722:        enum texicmd     cmd;
        !           723:
        !           724:        assert(0 == p->literal);
        !           725:
        !           726:        while (*pos < sz && '\n' != buf[*pos]) {
        !           727:                while (*pos < sz && isws(buf[*pos])) {
        !           728:                        p->seenws = 1;
        !           729:                        advance(p, buf, pos);
        !           730:                }
        !           731:                switch (buf[*pos]) {
        !           732:                case ('}'):
        !           733:                        if (0 == p->ign)
        !           734:                                texiwarn(p, "unexpected \"}\"");
        !           735:                        advance(p, buf, pos);
        !           736:                        continue;
        !           737:                case ('{'):
        !           738:                        if (0 == p->ign)
        !           739:                                texiwarn(p, "unexpected \"{\"");
1.1       kristaps  740:                        advance(p, buf, pos);
                    741:                        continue;
                    742:                case ('@'):
                    743:                        break;
                    744:                default:
                    745:                        texiword(p, buf, sz, pos);
                    746:                        continue;
                    747:                }
                    748:
                    749:                cmd = texicmd(p, buf, *pos, sz, &end);
                    750:                advanceto(p, buf, pos, end);
                    751:                if (TEXICMD__MAX == cmd)
                    752:                        continue;
                    753:                if (NULL != texitoks[cmd].fp)
                    754:                        (*texitoks[cmd].fp)(p, cmd, buf, sz, pos);
                    755:        }
                    756: }
                    757:
                    758: static void
1.3     ! kristaps  759: parsesingle(struct texi *p, const char *buf, size_t sz, size_t *pos)
        !           760: {
        !           761:        size_t           end;
        !           762:        enum texicmd     cmd;
        !           763:
        !           764:        if ((*pos = advancenext(p, buf, sz, pos)) >= sz)
        !           765:                return;
        !           766:
        !           767:        switch (buf[*pos]) {
        !           768:        case ('}'):
        !           769:                if (0 == p->ign)
        !           770:                        texiwarn(p, "unexpected \"}\"");
        !           771:                advance(p, buf, pos);
        !           772:                return;
        !           773:        case ('{'):
        !           774:                if (0 == p->ign)
        !           775:                        texiwarn(p, "unexpected \"{\"");
        !           776:                advance(p, buf, pos);
        !           777:                return;
        !           778:        case ('@'):
        !           779:                break;
        !           780:        default:
        !           781:                texiword(p, buf, sz, pos);
        !           782:                return;
        !           783:        }
        !           784:
        !           785:        cmd = texicmd(p, buf, *pos, sz, &end);
        !           786:        advanceto(p, buf, pos, end);
        !           787:        if (TEXICMD__MAX == cmd)
        !           788:                return;
        !           789:        if (NULL != texitoks[cmd].fp)
        !           790:                (*texitoks[cmd].fp)(p, cmd, buf, sz, pos);
        !           791: }
        !           792:
        !           793: static void
1.1       kristaps  794: parseto(struct texi *p, const char *buf,
                    795:        size_t sz, size_t *pos, const char *endtoken)
                    796: {
                    797:        size_t           end;
                    798:        enum texicmd     cmd;
                    799:        size_t           endtoksz;
                    800:
                    801:        endtoksz = strlen(endtoken);
                    802:        assert(endtoksz > 0);
                    803:
                    804:        while ((*pos = advancenext(p, buf, sz, pos)) < sz) {
                    805:                switch (buf[*pos]) {
                    806:                case ('}'):
1.3     ! kristaps  807:                        if (0 == p->ign)
        !           808:                                texiwarn(p, "unexpected \"}\"");
1.1       kristaps  809:                        advance(p, buf, pos);
                    810:                        continue;
                    811:                case ('{'):
1.3     ! kristaps  812:                        if (0 == p->ign)
        !           813:                                texiwarn(p, "unexpected \"{\"");
1.1       kristaps  814:                        advance(p, buf, pos);
                    815:                        continue;
                    816:                case ('@'):
                    817:                        break;
                    818:                default:
                    819:                        texiword(p, buf, sz, pos);
                    820:                        continue;
                    821:                }
                    822:
                    823:                cmd = texicmd(p, buf, *pos, sz, &end);
                    824:                advanceto(p, buf, pos, end);
                    825:                if (TEXICMD_END == cmd) {
1.2       kristaps  826:                        while (*pos < sz && isws(buf[*pos]))
1.1       kristaps  827:                                advance(p, buf, pos);
                    828:                        /*
                    829:                         * FIXME: skip tabs and also check the full
                    830:                         * word, not just its initial substring!
                    831:                         */
                    832:                        if (sz - *pos >= endtoksz && 0 == strncmp
                    833:                                 (&buf[*pos], endtoken, endtoksz)) {
1.3     ! kristaps  834:                                advanceeoln(p, buf, sz, pos, 0);
1.1       kristaps  835:                                break;
                    836:                        }
1.3     ! kristaps  837:                        if (0 == p->ign)
        !           838:                                texiwarn(p, "unexpected \"end\"");
        !           839:                        advanceeoln(p, buf, sz, pos, 0);
1.1       kristaps  840:                        continue;
                    841:                } else if (TEXICMD__MAX != cmd)
                    842:                        if (NULL != texitoks[cmd].fp)
                    843:                                (*texitoks[cmd].fp)(p, cmd, buf, sz, pos);
                    844:        }
                    845: }
                    846:
                    847: static void
1.2       kristaps  848: parsefile(struct texi *p, const char *fname)
                    849: {
                    850:        struct texifile  *f;
                    851:        int               fd;
                    852:        struct stat       st;
                    853:
                    854:        assert(p->filepos < 64);
                    855:        f = &p->files[p->filepos];
                    856:        memset(f, 0, sizeof(struct texifile));
                    857:
                    858:        f->name = fname;
                    859:        if (-1 == (fd = open(fname, O_RDONLY, 0))) {
                    860:                texiabort(p, fname);
                    861:        } else if (-1 == fstat(fd, &st)) {
                    862:                close(fd);
                    863:                texiabort(p, fname);
                    864:        }
                    865:
                    866:        f->mapsz = st.st_size;
                    867:        f->map = mmap(NULL, f->mapsz,
                    868:                PROT_READ, MAP_SHARED, fd, 0);
                    869:        close(fd);
                    870:
                    871:        if (MAP_FAILED == f->map)
                    872:                texiabort(p, fname);
                    873:
                    874:        p->filepos++;
                    875:        parseeof(p, f->map, f->mapsz);
                    876:        texifilepop(p);
                    877: }
                    878:
                    879: static void
1.3     ! kristaps  880: dodeftypevar(struct texi *p, enum texicmd cmd,
        !           881:        const char *buf, size_t sz, size_t *pos)
        !           882: {
        !           883:        const char      *blk;
        !           884:
        !           885:        blk = TEXICMD_DEFTYPEVR == cmd ?
        !           886:                "deftypevr" : "deftypevar";
        !           887:
        !           888:        if (p->ign) {
        !           889:                parseto(p, buf, sz, pos, blk);
        !           890:                return;
        !           891:        }
        !           892:
        !           893:        teximacro(p, ".Pp");
        !           894:        if (TEXICMD_DEFTYPEVR == cmd) {
        !           895:                parsebracket(p, buf, sz, pos);
        !           896:                texiputchars(p, ":\n");
        !           897:        }
        !           898:        p->literal++;
        !           899:        teximacroopen(p, ".Vt ");
        !           900:        while (*pos < sz && '\n' != buf[*pos])
        !           901:                parsesingle(p, buf, sz, pos);
        !           902:        teximacroclose(p);
        !           903:        p->literal--;
        !           904:        teximacro(p, ".Pp");
        !           905:        parseto(p, buf, sz, pos, blk);
        !           906: }
        !           907:
        !           908: static void
        !           909: dodeftypefun(struct texi *p, enum texicmd cmd,
        !           910:        const char *buf, size_t sz, size_t *pos)
        !           911: {
        !           912:        const char      *blk;
        !           913:
        !           914:        switch (cmd) {
        !           915:        case (TEXICMD_DEFTYPEFN):
        !           916:                blk = "deftypefn";
        !           917:                break;
        !           918:        case (TEXICMD_DEFTYPEFUN):
        !           919:                blk = "deftypefun";
        !           920:                break;
        !           921:        case (TEXICMD_DEFTYPEFNX):
        !           922:        case (TEXICMD_DEFTYPEFUNX):
        !           923:                blk = NULL;
        !           924:                break;
        !           925:        default:
        !           926:                abort();
        !           927:        }
        !           928:
        !           929:        if (p->ign) {
        !           930:                if (NULL != blk)
        !           931:                        parseto(p, buf, sz, pos, blk);
        !           932:                return;
        !           933:        }
        !           934:
        !           935:        switch (cmd) {
        !           936:        case (TEXICMD_DEFTYPEFN):
        !           937:        case (TEXICMD_DEFTYPEFUN):
        !           938:                teximacro(p, ".Pp");
        !           939:                break;
        !           940:        default:
        !           941:                break;
        !           942:        }
        !           943:        if (TEXICMD_DEFTYPEFN == cmd ||
        !           944:                        TEXICMD_DEFTYPEFNX == cmd) {
        !           945:                parsebracket(p, buf, sz, pos);
        !           946:                texiputchars(p, ":\n");
        !           947:        }
        !           948:        teximacroopen(p, ".Ft ");
        !           949:        parsesingle(p, buf, sz, pos);
        !           950:        teximacroclose(p);
        !           951:        teximacroopen(p, ".Fn ");
        !           952:        parsesingle(p, buf, sz, pos);
        !           953:        teximacroclose(p);
        !           954:        teximacroopen(p, ".Li ");
        !           955:        while (*pos < sz && '\n' != buf[*pos])
        !           956:                parsesingle(p, buf, sz, pos);
        !           957:        teximacroclose(p);
        !           958:        teximacro(p, ".Pp");
        !           959:        if (NULL != blk)
        !           960:                parseto(p, buf, sz, pos, blk);
        !           961: }
        !           962:
        !           963: static void
1.1       kristaps  964: doignblock(struct texi *p, enum texicmd cmd,
                    965:        const char *buf, size_t sz, size_t *pos)
                    966: {
1.3     ! kristaps  967:        const char      *blk;
1.1       kristaps  968:
                    969:        switch (cmd) {
                    970:        case (TEXICMD_COPYING):
1.3     ! kristaps  971:                blk = "copying";
1.1       kristaps  972:                break;
                    973:        case (TEXICMD_DETAILMENU):
1.3     ! kristaps  974:                blk = "detailmenu";
1.1       kristaps  975:                break;
                    976:        case (TEXICMD_DIRENTRY):
1.3     ! kristaps  977:                blk = "direntry";
        !           978:                break;
        !           979:        case (TEXICMD_IFCLEAR):
        !           980:                blk = "ifclear";
1.1       kristaps  981:                break;
                    982:        case (TEXICMD_IFHTML):
1.3     ! kristaps  983:                blk = "ifhtml";
        !           984:                break;
        !           985:        case (TEXICMD_IFINFO):
        !           986:                blk = "ifinfo";
        !           987:                break;
        !           988:        case (TEXICMD_IFSET):
        !           989:                blk = "ifset";
1.1       kristaps  990:                break;
                    991:        case (TEXICMD_IFTEX):
1.3     ! kristaps  992:                blk = "iftex";
1.1       kristaps  993:                break;
                    994:        case (TEXICMD_MENU):
1.3     ! kristaps  995:                blk = "menu";
1.1       kristaps  996:                break;
                    997:        case (TEXICMD_TEX):
1.3     ! kristaps  998:                blk = "tex";
1.1       kristaps  999:                break;
                   1000:        case (TEXICMD_TITLEPAGE):
1.3     ! kristaps 1001:                blk = "titlepage";
1.1       kristaps 1002:                break;
                   1003:        default:
                   1004:                abort();
                   1005:        }
1.3     ! kristaps 1006:        p->ign++;
        !          1007:        parseto(p, buf, sz, pos, blk);
        !          1008:        p->ign--;
1.1       kristaps 1009: }
                   1010:
                   1011: static void
1.3     ! kristaps 1012: doblock(struct texi *p, enum texicmd cmd,
1.1       kristaps 1013:        const char *buf, size_t sz, size_t *pos)
                   1014: {
1.3     ! kristaps 1015:        const char      *blk;
        !          1016:
        !          1017:        switch (cmd) {
        !          1018:        case (TEXICMD_GROUP):
        !          1019:                blk = "group";
        !          1020:                break;
        !          1021:        case (TEXICMD_IFNOTTEX):
        !          1022:                blk = "ifnottex";
        !          1023:                break;
        !          1024:        default:
        !          1025:                abort();
        !          1026:        }
1.1       kristaps 1027:
1.3     ! kristaps 1028:        parseto(p, buf, sz, pos, blk);
1.1       kristaps 1029: }
                   1030:
                   1031: static void
                   1032: doinline(struct texi *p, const char *buf,
                   1033:        size_t sz, size_t *pos, const char *macro)
                   1034: {
1.3     ! kristaps 1035:        int      open = 0;
        !          1036:
        !          1037:        if (0 == p->outmacro) {
        !          1038:                open = 1;
        !          1039:                teximacroopen(p, ".");
        !          1040:        } else
        !          1041:                texiputchar(p, ' ');
1.1       kristaps 1042:
                   1043:        texiputchars(p, macro);
                   1044:        texiputchar(p, ' ');
                   1045:        p->seenws = 0;
                   1046:        parsebracket(p, buf, sz, pos);
                   1047:        if (*pos < sz - 1 &&
                   1048:                 ismpunct(buf[*pos]) &&
                   1049:                 isspace(buf[*pos + 1])) {
                   1050:                texiputchar(p, ' ');
                   1051:                texiputchar(p, buf[*pos]);
                   1052:                advance(p, buf, pos);
                   1053:        }
1.3     ! kristaps 1054:        if (open)
        !          1055:                teximacroclose(p);
1.1       kristaps 1056: }
                   1057:
                   1058: static void
1.2       kristaps 1059: doinclude(struct texi *p, enum texicmd cmd,
                   1060:        const char *buf, size_t sz, size_t *pos)
                   1061: {
                   1062:        char     fname[PATH_MAX], path[PATH_MAX];
                   1063:        size_t   i;
                   1064:        int      rc;
                   1065:
                   1066:        while (*pos < sz && ' ' == buf[*pos])
                   1067:                advance(p, buf, pos);
                   1068:
                   1069:        /* Read in the filename. */
                   1070:        for (i = 0; *pos < sz && '\n' != buf[*pos]; i++) {
                   1071:                if (i == sizeof(fname) - 1)
                   1072:                        break;
                   1073:                fname[i] = buf[*pos];
                   1074:                advance(p, buf, pos);
                   1075:        }
                   1076:
                   1077:        if (i == 0)
                   1078:                texierr(p, "path too short");
                   1079:        else if ('\n' != buf[*pos])
                   1080:                texierr(p, "path too long");
                   1081:        else if ('/' == fname[0])
                   1082:                texierr(p, "no absolute paths");
                   1083:        fname[i] = '\0';
                   1084:
                   1085:        if (strstr(fname, "../") || strstr(fname, "/.."))
                   1086:                texierr(p, "insecure path");
                   1087:
                   1088:        /* Append filename to original name's directory. */
                   1089:        rc = snprintf(path, sizeof(path), "%s/%s", p->dir, fname);
                   1090:        if (rc < 0)
                   1091:                texierr(p, "couldn't format filename");
                   1092:        else if ((size_t)rc >= sizeof(path))
                   1093:                texierr(p, "path too long");
                   1094:
                   1095:        /* Pump through to parser. */
                   1096:        parsefile(p, path);
                   1097: }
                   1098:
                   1099: static void
1.1       kristaps 1100: doitalic(struct texi *p, enum texicmd cmd,
                   1101:        const char *buf, size_t sz, size_t *pos)
                   1102: {
                   1103:
                   1104:        texiputchars(p, "\\fI");
                   1105:        parsebracket(p, buf, sz, pos);
                   1106:        texiputchars(p, "\\fP");
                   1107: }
                   1108:
                   1109: static void
1.3     ! kristaps 1110: doenv(struct texi *p, enum texicmd cmd,
        !          1111:        const char *buf, size_t sz, size_t *pos)
        !          1112: {
        !          1113:
        !          1114:        if (p->literal)
        !          1115:                parsebracket(p, buf, sz, pos);
        !          1116:        else
        !          1117:                doinline(p, buf, sz, pos, "Ev");
        !          1118: }
        !          1119:
        !          1120: static void
1.1       kristaps 1121: doliteral(struct texi *p, enum texicmd cmd,
                   1122:        const char *buf, size_t sz, size_t *pos)
                   1123: {
                   1124:
1.3     ! kristaps 1125:        if (p->literal)
1.1       kristaps 1126:                parsebracket(p, buf, sz, pos);
                   1127:        else
                   1128:                doinline(p, buf, sz, pos, "Li");
                   1129: }
                   1130:
                   1131: static void
                   1132: doemph(struct texi *p, enum texicmd cmd,
                   1133:        const char *buf, size_t sz, size_t *pos)
                   1134: {
                   1135:
1.3     ! kristaps 1136:        if (p->literal)
1.1       kristaps 1137:                doitalic(p, cmd, buf, sz, pos);
                   1138:        else
                   1139:                doinline(p, buf, sz, pos, "Em");
                   1140: }
                   1141:
                   1142: static void
                   1143: docommand(struct texi *p, enum texicmd cmd,
                   1144:        const char *buf, size_t sz, size_t *pos)
                   1145: {
                   1146:
                   1147:        doinline(p, buf, sz, pos, "Xr");
                   1148: }
                   1149:
                   1150: static void
                   1151: dobracket(struct texi *p, enum texicmd cmd,
                   1152:        const char *buf, size_t sz, size_t *pos)
                   1153: {
                   1154:
                   1155:        parsebracket(p, buf, sz, pos);
                   1156: }
                   1157:
                   1158: static void
                   1159: dofile(struct texi *p, enum texicmd cmd,
                   1160:        const char *buf, size_t sz, size_t *pos)
                   1161: {
                   1162:
1.3     ! kristaps 1163:        if (p->literal)
1.1       kristaps 1164:                parsebracket(p, buf, sz, pos);
                   1165:        else
                   1166:                doinline(p, buf, sz, pos, "Pa");
                   1167: }
                   1168:
                   1169: static void
1.3     ! kristaps 1170: dodisplay(struct texi *p, enum texicmd cmd,
        !          1171:        const char *buf, size_t sz, size_t *pos)
        !          1172: {
        !          1173:
        !          1174:        if (p->outmacro)
        !          1175:                texierr(p, "display in open line scope!?");
        !          1176:        else if (p->literal)
        !          1177:                texierr(p, "display in a literal scope!?");
        !          1178:
        !          1179:        teximacro(p, ".Bd -display");
        !          1180:        advanceeoln(p, buf, sz, pos, 1);
        !          1181:        parseto(p, buf, sz, pos, "display");
        !          1182:        teximacro(p, ".Ed");
        !          1183: }
        !          1184:
        !          1185: static void
1.1       kristaps 1186: doexample(struct texi *p, enum texicmd cmd,
                   1187:        const char *buf, size_t sz, size_t *pos)
                   1188: {
1.3     ! kristaps 1189:        const char      *blk;
        !          1190:
        !          1191:        if (p->outmacro)
        !          1192:                texierr(p, "example in open line scope!?");
        !          1193:        else if (p->literal)
        !          1194:                texierr(p, "example in a literal scope!?");
        !          1195:
        !          1196:        blk = TEXICMD_EXAMPLE == cmd ?  "example" : "smallexample";
1.1       kristaps 1197:
                   1198:        teximacro(p, ".Bd -literal");
1.3     ! kristaps 1199:        advanceeoln(p, buf, sz, pos, 1);
        !          1200:        p->literal++;
        !          1201:        parseto(p, buf, sz, pos, blk);
        !          1202:        p->literal--;
1.1       kristaps 1203:        teximacro(p, ".Ed");
                   1204: }
                   1205:
                   1206: static void
                   1207: dobye(struct texi *p, enum texicmd cmd,
                   1208:        const char *buf, size_t sz, size_t *pos)
                   1209: {
                   1210:
                   1211:        texiexit(p);
                   1212:        exit(EXIT_SUCCESS);
                   1213: }
                   1214:
                   1215: static void
                   1216: dosymbol(struct texi *p, enum texicmd cmd,
                   1217:        const char *buf, size_t sz, size_t *pos)
                   1218: {
                   1219:
1.3     ! kristaps 1220:        if (p->seenws && p->outcol && 0 == p->literal) {
        !          1221:                texiputchar(p, ' ');
        !          1222:                p->seenws = 0;
        !          1223:        }
        !          1224:
1.1       kristaps 1225:        switch (cmd) {
1.3     ! kristaps 1226:        case (TEXICMD_ASTERISK):
        !          1227:        case (TEXICMD_NEWLINE):
        !          1228:        case (TEXICMD_SPACE):
        !          1229:        case (TEXICMD_TAB):
        !          1230:                texiputchar(p, ' ');
        !          1231:                break;
1.1       kristaps 1232:        case (TEXICMD_AT):
1.3     ! kristaps 1233:                texiputchar(p, '@');
        !          1234:                break;
        !          1235:        case (TEXICMD_BANG):
        !          1236:                texiputchar(p, '!');
1.1       kristaps 1237:                break;
                   1238:        case (TEXICMD_COPYRIGHT):
                   1239:                texiputchars(p, "\\(co");
                   1240:                break;
1.2       kristaps 1241:        case (TEXICMD_DOTS):
                   1242:                texiputchars(p, "...");
                   1243:                break;
1.1       kristaps 1244:        case (TEXICMD_LATEX):
                   1245:                texiputchars(p, "LaTeX");
                   1246:                break;
1.3     ! kristaps 1247:        case (TEXICMD_QUESTIONMARK):
        !          1248:                texiputchar(p, '?');
        !          1249:                break;
        !          1250:        case (TEXICMD_SQUIGGLE_LEFT):
        !          1251:                texiputchars(p, "{");
        !          1252:                break;
        !          1253:        case (TEXICMD_SQUIGGLE_RIGHT):
        !          1254:                texiputchars(p, "}");
        !          1255:                break;
1.1       kristaps 1256:        case (TEXICMD_TEXSYM):
                   1257:                texiputchars(p, "TeX");
                   1258:                break;
1.3     ! kristaps 1259:        case (TEXICMD_COLON):
        !          1260:        case (TEXICMD_HYPHEN):
        !          1261:                break;
1.1       kristaps 1262:        default:
                   1263:                abort();
                   1264:        }
                   1265:
                   1266:        doignbracket(p, cmd, buf, sz, pos);
                   1267: }
                   1268:
                   1269: static void
                   1270: doquotation(struct texi *p, enum texicmd cmd,
                   1271:        const char *buf, size_t sz, size_t *pos)
                   1272: {
                   1273:
                   1274:        teximacro(p, ".Qo");
                   1275:        parseto(p, buf, sz, pos, "quotation");
                   1276:        teximacro(p, ".Qc");
                   1277: }
                   1278:
1.3     ! kristaps 1279: /* FIXME */
        !          1280: static void
        !          1281: domath(struct texi *p, enum texicmd cmd,
        !          1282:        const char *buf, size_t sz, size_t *pos)
        !          1283: {
        !          1284:        size_t   nest;
        !          1285:
        !          1286:        /*
        !          1287:         * Math handling is different from everything else.
        !          1288:         * We don't allow any subcomponents, and we ignore the rules in
        !          1289:         * terms of @-commands.
        !          1290:         * This departs from GNU's rules, but whatever.
        !          1291:         */
        !          1292:        while (*pos < sz && isws(buf[*pos]))
        !          1293:                advance(p, buf, pos);
        !          1294:        if (*pos == sz || '{' != buf[*pos])
        !          1295:                return;
        !          1296:        advance(p, buf, pos);
        !          1297:        if (p->seenws && p->outcol && 0 == p->literal)
        !          1298:                texiputchar(p, ' ');
        !          1299:        p->seenws = 0;
        !          1300:        for (nest = 1; *pos < sz && nest > 0; ) {
        !          1301:                if ('{' == buf[*pos])
        !          1302:                        nest++;
        !          1303:                else if ('}' == buf[*pos])
        !          1304:                        if (0 == --nest)
        !          1305:                                continue;
        !          1306:                texiputchar(p, buf[*pos]);
        !          1307:                advance(p, buf, pos);
        !          1308:        }
        !          1309:        if (*pos == sz)
        !          1310:                return;
        !          1311:        assert('}' == buf[*pos]);
        !          1312:        advance(p, buf, pos);
        !          1313: }
        !          1314:
        !          1315: /* FIXME */
1.1       kristaps 1316: static void
                   1317: doarg1(struct texi *p, enum texicmd cmd,
                   1318:        const char *buf, size_t sz, size_t *pos)
                   1319: {
1.3     ! kristaps 1320:        int      open = 0;
1.1       kristaps 1321:
                   1322:        if (*pos == sz || '{' != buf[*pos])
                   1323:                return;
                   1324:        advance(p, buf, pos);
                   1325:        switch (cmd) {
                   1326:        case (TEXICMD_EMAIL):
1.3     ! kristaps 1327:                if ( ! p->outmacro) {
        !          1328:                        open = 1;
        !          1329:                        teximacroopen(p, ".");
        !          1330:                }
1.1       kristaps 1331:                texiputchars(p, "Lk ");
                   1332:                break;
1.3     ! kristaps 1333:        case (TEXICMD_UREF):
1.1       kristaps 1334:        case (TEXICMD_URL):
1.3     ! kristaps 1335:                if ( ! p->outmacro) {
        !          1336:                        open = 1;
        !          1337:                        teximacroopen(p, ".");
        !          1338:                }
1.1       kristaps 1339:                texiputchars(p, "Mt ");
                   1340:                break;
                   1341:        default:
1.3     ! kristaps 1342:                break;
1.1       kristaps 1343:        }
1.3     ! kristaps 1344:        /* FIXME: this is ugly */
1.1       kristaps 1345:        while (*pos < sz && '}' != buf[*pos] && ',' != buf[*pos]) {
                   1346:                texiputchar(p, buf[*pos]);
                   1347:                advance(p, buf, pos);
                   1348:        }
                   1349:        while (*pos < sz && '}' != buf[*pos])
                   1350:                advance(p, buf, pos);
                   1351:        if (*pos < sz)
                   1352:                advance(p, buf, pos);
                   1353:        if (*pos < sz - 1 &&
                   1354:                 ismpunct(buf[*pos]) &&
                   1355:                 isspace(buf[*pos + 1])) {
                   1356:                texiputchar(p, ' ');
                   1357:                texiputchar(p, buf[*pos]);
                   1358:                advance(p, buf, pos);
                   1359:        }
1.3     ! kristaps 1360:        if (open)
        !          1361:                teximacroclose(p);
1.1       kristaps 1362: }
                   1363:
                   1364: static void
                   1365: dosubsection(struct texi *p, enum texicmd cmd,
                   1366:                const char *buf, size_t sz, size_t *pos)
                   1367: {
                   1368:
1.3     ! kristaps 1369:        if (p->outmacro)
        !          1370:                texierr(p, "subsubsection in open line scope!?");
        !          1371:        else if (p->literal)
        !          1372:                texierr(p, "subsubsection in a literal scope!?");
        !          1373:
        !          1374:        teximacro(p, ".Pp");
        !          1375:        parseeoln(p, buf, sz, pos);
        !          1376:        teximacro(p, ".Pp");
1.1       kristaps 1377: }
                   1378:
                   1379: static void
                   1380: dosection(struct texi *p, enum texicmd cmd,
                   1381:                const char *buf, size_t sz, size_t *pos)
                   1382: {
                   1383:
1.3     ! kristaps 1384:        if (p->outmacro)
        !          1385:                texierr(p, "subsection in open line scope!?");
        !          1386:        else if (p->literal)
        !          1387:                texierr(p, "subsection in a literal scope!?");
        !          1388:
        !          1389:        teximacroopen(p, ".Ss ");
        !          1390:        parseeoln(p, buf, sz, pos);
        !          1391:        teximacroclose(p);
        !          1392: }
        !          1393:
        !          1394: static void
        !          1395: dosp(struct texi *p, enum texicmd cmd,
        !          1396:        const char *buf, size_t sz, size_t *pos)
        !          1397: {
        !          1398:
        !          1399:        if (p->outmacro)
        !          1400:                texierr(p, "spacing in open line scope!?");
        !          1401:        else if (p->literal)
        !          1402:                texierr(p, "spacing in a literal scope!?");
        !          1403:
        !          1404:        teximacro(p, ".Pp");
        !          1405:        advanceeoln(p, buf, sz, pos, 1);
1.1       kristaps 1406: }
                   1407:
                   1408: static void
1.3     ! kristaps 1409: dochapter(struct texi *p, enum texicmd cmd,
1.1       kristaps 1410:        const char *buf, size_t sz, size_t *pos)
                   1411: {
                   1412:
1.3     ! kristaps 1413:        if (p->outmacro)
        !          1414:                texierr(p, "section in open line scope!?");
        !          1415:        else if (p->literal)
        !          1416:                texierr(p, "section in a literal scope!?");
        !          1417:
        !          1418:        teximacroopen(p, ".Sh ");
        !          1419:        parseeoln(p, buf, sz, pos);
        !          1420:        teximacroclose(p);
1.1       kristaps 1421: }
                   1422:
                   1423: static void
                   1424: dotop(struct texi *p, enum texicmd cmd,
                   1425:        const char *buf, size_t sz, size_t *pos)
                   1426: {
                   1427:
1.3     ! kristaps 1428:        p->ign--;
        !          1429:        advanceeoln(p, buf, sz, pos, 1);
        !          1430:        teximacro(p, ".Dd $Mdocdate: February 17 2015 $");
1.1       kristaps 1431:        teximacro(p, ".Dt SOMETHING 7");
                   1432:        teximacro(p, ".Os");
                   1433:        teximacro(p, ".Sh NAME");
                   1434:        teximacro(p, ".Nm Something");
                   1435:        teximacro(p, ".Nd Something");
                   1436: }
                   1437:
                   1438: static void
                   1439: doitem(struct texi *p, enum texicmd cmd,
                   1440:        const char *buf, size_t sz, size_t *pos)
                   1441: {
                   1442:
1.3     ! kristaps 1443:        if (p->outmacro)
        !          1444:                texierr(p, "item in open line scope!?");
        !          1445:        else if (p->literal)
        !          1446:                texierr(p, "item in a literal scope!?");
        !          1447:
        !          1448:        switch (p->list) {
        !          1449:        case (TEXILIST_ITEM):
        !          1450:                teximacroopen(p, ".It");
        !          1451:                break;
        !          1452:        case (TEXILIST_NOITEM):
        !          1453:                teximacro(p, ".It");
        !          1454:                break;
        !          1455:        default:
        !          1456:                teximacro(p, ".Pp");
        !          1457:                break;
        !          1458:        }
        !          1459:
        !          1460:        parseeoln(p, buf, sz, pos);
1.1       kristaps 1461:
1.3     ! kristaps 1462:        if (TEXILIST_ITEM == p->list)
        !          1463:                teximacroclose(p);
        !          1464:        else
1.1       kristaps 1465:                texiputchar(p, '\n');
                   1466: }
                   1467:
                   1468: static void
                   1469: dotable(struct texi *p, enum texicmd cmd,
                   1470:        const char *buf, size_t sz, size_t *pos)
                   1471: {
1.3     ! kristaps 1472:        enum texilist   sv = p->list;
        !          1473:
        !          1474:        if (p->outmacro)
        !          1475:                texierr(p, "table in open line scope!?");
        !          1476:        else if (p->literal)
        !          1477:                texierr(p, "table in a literal scope!?");
1.1       kristaps 1478:
1.3     ! kristaps 1479:        p->list = TEXILIST_ITEM;
1.1       kristaps 1480:        teximacro(p, ".Bl -tag -width Ds");
                   1481:        parseto(p, buf, sz, pos, "table");
                   1482:        teximacro(p, ".El");
1.3     ! kristaps 1483:        p->list = sv;
1.1       kristaps 1484: }
                   1485:
                   1486: static void
1.2       kristaps 1487: doenumerate(struct texi *p, enum texicmd cmd,
                   1488:        const char *buf, size_t sz, size_t *pos)
                   1489: {
1.3     ! kristaps 1490:        enum texilist    sv = p->list;
1.2       kristaps 1491:
1.3     ! kristaps 1492:        if (p->outmacro)
        !          1493:                texierr(p, "enumerate in open line scope!?");
        !          1494:        else if (p->literal)
        !          1495:                texierr(p, "enumerate in a literal scope!?");
        !          1496:
        !          1497:        p->list = TEXILIST_NOITEM;
1.2       kristaps 1498:        teximacro(p, ".Bl -enum");
                   1499:        parseto(p, buf, sz, pos, "enumerate");
                   1500:        teximacro(p, ".El");
1.3     ! kristaps 1501:        p->list = sv;
1.2       kristaps 1502: }
                   1503:
                   1504: static void
1.1       kristaps 1505: doitemize(struct texi *p, enum texicmd cmd,
                   1506:        const char *buf, size_t sz, size_t *pos)
                   1507: {
1.3     ! kristaps 1508:        enum texilist   sv = p->list;
        !          1509:
        !          1510:        if (p->outmacro)
        !          1511:                texierr(p, "itemize in open line scope!?");
        !          1512:        else if (p->literal)
        !          1513:                texierr(p, "itemize in a literal scope!?");
1.1       kristaps 1514:
1.3     ! kristaps 1515:        p->list = TEXILIST_ITEM;
1.1       kristaps 1516:        teximacro(p, ".Bl -bullet");
                   1517:        parseto(p, buf, sz, pos, "itemize");
                   1518:        teximacro(p, ".El");
1.3     ! kristaps 1519:        p->list = sv;
1.1       kristaps 1520: }
                   1521:
                   1522: static void
                   1523: doignbracket(struct texi *p, enum texicmd cmd,
                   1524:        const char *buf, size_t sz, size_t *pos)
                   1525: {
                   1526:
1.3     ! kristaps 1527:        p->ign++;
1.1       kristaps 1528:        parsebracket(p, buf, sz, pos);
1.3     ! kristaps 1529:        p->ign--;
1.1       kristaps 1530: }
                   1531:
                   1532: static void
                   1533: doignline(struct texi *p, enum texicmd cmd,
                   1534:        const char *buf, size_t sz, size_t *pos)
                   1535: {
                   1536:
1.3     ! kristaps 1537:        advanceeoln(p, buf, sz, pos, 1);
1.1       kristaps 1538: }
                   1539:
                   1540: int
                   1541: main(int argc, char *argv[])
                   1542: {
                   1543:        struct texi      texi;
1.2       kristaps 1544:        int              c;
                   1545:        char            *path, *dir;
1.1       kristaps 1546:        const char      *progname;
                   1547:
                   1548:        progname = strrchr(argv[0], '/');
                   1549:        if (progname == NULL)
                   1550:                progname = argv[0];
                   1551:        else
                   1552:                ++progname;
                   1553:
                   1554:        while (-1 != (c = getopt(argc, argv, "")))
                   1555:                switch (c) {
                   1556:                default:
                   1557:                        goto usage;
                   1558:                }
                   1559:
                   1560:        argv += optind;
                   1561:        if (0 == (argc -= optind))
                   1562:                goto usage;
                   1563:
1.2       kristaps 1564:        if (NULL == (path = strdup(argv[0]))) {
                   1565:                perror(NULL);
                   1566:                exit(EXIT_FAILURE);
                   1567:        } else if (NULL == (dir = dirname(path))) {
                   1568:                perror(argv[0]);
                   1569:                free(path);
                   1570:                exit(EXIT_FAILURE);
                   1571:        }
                   1572:        free(path);
                   1573:
1.1       kristaps 1574:        memset(&texi, 0, sizeof(struct texi));
1.3     ! kristaps 1575:        texi.ign = 1;
1.2       kristaps 1576:        texi.dir = strdup(dir);
                   1577:        parsefile(&texi, argv[0]);
                   1578:        texiexit(&texi);
                   1579:        return(EXIT_FAILURE);
1.1       kristaps 1580: usage:
                   1581:        fprintf(stderr, "usage: %s file\n", progname);
                   1582:        return(EXIT_FAILURE);
                   1583: }

CVSweb