=================================================================== RCS file: /cvs/texi2mdoc/util.c,v retrieving revision 1.14 retrieving revision 1.21 diff -u -p -r1.14 -r1.21 --- texi2mdoc/util.c 2015/02/25 14:37:17 1.14 +++ texi2mdoc/util.c 2015/03/01 13:39:51 1.21 @@ -1,4 +1,4 @@ -/* $Id: util.c,v 1.14 2015/02/25 14:37:17 kristaps Exp $ */ +/* $Id: util.c,v 1.21 2015/03/01 13:39:51 kristaps Exp $ */ /* * Copyright (c) 2015 Kristaps Dzonsons * @@ -20,8 +20,6 @@ #include #include #include -#include -#include #include #include #include @@ -79,7 +77,9 @@ texiexit(struct texi *p) /* Make sure we're newline-terminated. */ if (p->outcol) - putchar('\n'); + fputc('\n', p->outfile); + if (NULL != p->chapters) + teximdocclose(p, 1); /* Unmap all files. */ while (p->filepos > 0) @@ -122,12 +122,19 @@ texiabort(struct texi *p, const char *errstring) void texiwarn(const struct texi *p, const char *fmt, ...) { - va_list ap; + va_list ap; + const struct texifile *f; - fprintf(stderr, "%s:%zu:%zu: warning: ", - p->files[p->filepos - 1].name, - p->files[p->filepos - 1].line + 1, - p->files[p->filepos - 1].col + 1); + f = &p->files[p->filepos - 1]; + + if (f->insplice) + fprintf(stderr, "%s:%zu:%zu (%zuB left in splice): " + "warning: ", f->name, f->line + 1, + f->col + 1, f->insplice); + else + fprintf(stderr, "%s:%zu:%zu: warning: ", + f->name, f->line + 1, f->col + 1); + va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); @@ -141,12 +148,19 @@ texiwarn(const struct texi *p, const char *fmt, ...) void texierr(struct texi *p, const char *fmt, ...) { - va_list ap; + va_list ap; + struct texifile *f; - fprintf(stderr, "%s:%zu:%zu: error: ", - p->files[p->filepos - 1].name, - p->files[p->filepos - 1].line + 1, - p->files[p->filepos - 1].col + 1); + f = &p->files[p->filepos - 1]; + + if (f->insplice) + fprintf(stderr, "%s:%zu:%zu: (%zuB left in splice): " + "error: ", f->name, f->line + 1, + f->col + 1, f->insplice); + else + fprintf(stderr, "%s:%zu:%zu: error: ", + f->name, f->line + 1, f->col + 1); + va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); @@ -166,13 +180,13 @@ texiputchar(struct texi *p, char c) if (p->ign) return; if ('.' == c && 0 == p->outcol) - fputs("\\&", stdout); + fputs("\\&", p->outfile); if ('\'' == c && 0 == p->outcol) - fputs("\\&", stdout); + fputs("\\&", p->outfile); - putchar(c); + fputc(c, p->outfile); if ('\\' == c) - putchar('e'); + fputc('e', p->outfile); p->seenvs = 0; if ('\n' == c) { p->outcol = 0; @@ -193,10 +207,10 @@ texiputchars(struct texi *p, const char *s) if (p->ign) return; if ('.' == *s && 0 == p->outcol) - fputs("\\&", stdout); + fputs("\\&", p->outfile); if ('\'' == *s && 0 == p->outcol) - fputs("\\&", stdout); - p->outcol += fputs(s, stdout); + fputs("\\&", p->outfile); + p->outcol += fputs(s, p->outfile); p->seenvs = 0; } @@ -225,7 +239,7 @@ teximacroclose(struct texi *p) return; if (0 == --p->outmacro) { - putchar('\n'); + fputc('\n', p->outfile); p->outcol = p->seenws = 0; } } @@ -245,19 +259,19 @@ teximacroopen(struct texi *p, const char *s) return; if (p->outcol && 0 == p->outmacro) { - putchar('\n'); + fputc('\n', p->outfile); p->outcol = 0; } if (0 == p->outmacro) - putchar('.'); + fputc('.', p->outfile); else - putchar(' '); + fputc(' ', p->outfile); - if (EOF != (rc = fputs(s, stdout))) + if (EOF != (rc = fputs(s, p->outfile))) p->outcol += rc; - putchar(' '); + fputc(' ', p->outfile); p->outcol++; p->outmacro++; p->seenws = 0; @@ -279,10 +293,11 @@ teximacro(struct texi *p, const char *s) texierr(p, "\"%s\" in a literal scope!?", s); if (p->outcol) - putchar('\n'); + fputc('\n', p->outfile); - putchar('.'); - puts(s); + fputc('.', p->outfile); + fputs(s, p->outfile); + fputc('\n', p->outfile); p->outcol = p->seenws = 0; } @@ -306,13 +321,22 @@ texivspace(struct texi *p) void advance(struct texi *p, size_t *pos) { + struct texifile *f; - if ('\n' == BUF(p)[*pos]) { - p->files[p->filepos - 1].line++; - p->files[p->filepos - 1].col = 0; - } else - p->files[p->filepos - 1].col++; + f = &p->files[p->filepos - 1]; + if (0 == f->insplice) { + if ('\n' == BUF(p)[*pos]) { + f->line++; + f->col = 0; + } else + f->col++; + } else { + --f->insplice; + if (0 == f->insplice) + f->depth = 0; + } + (*pos)++; } @@ -419,7 +443,7 @@ advanceto(struct texi *p, size_t *pos, size_t end) } static void -texiexecmacro(struct texi *p, struct teximacro *m, size_t *pos) +texiexecmacro(struct texi *p, struct teximacro *m, size_t sv, size_t *pos) { size_t valsz, realsz, aasz, asz, ssz, i, j, k, start, end; @@ -427,17 +451,31 @@ texiexecmacro(struct texi *p, struct teximacro *m, siz char **args; const char *cp; + /* Disregard empty macros. */ + if (0 == (valsz = realsz = strlen(m->value))) + return; + + /* + * This is important: it protect us from macros that invoke more + * macros, possibly going on infinitely. + * We use "sv" instead of the current position because we might + * be invoked at the end of the macro (i.e., insplice == 0). + * The "sv" value was initialised at the start of the macro. + */ + if (sv > 0) + if (++p->files[p->filepos].depth > 64) + texierr(p, "maximium recursive depth"); + args = argparse(p, pos, &asz, m->argsz); if (asz != m->argsz) texiwarn(p, "invalid macro argument length"); aasz = asz < m->argsz ? asz : m->argsz; if (0 == aasz) { - texisplice(p, m->value, strlen(m->value), pos); + texisplice(p, m->value, valsz, *pos); return; } - valsz = realsz = strlen(m->value); val = strdup(m->value); for (i = j = 0; i < realsz; i++) { @@ -497,7 +535,7 @@ texiexecmacro(struct texi *p, struct teximacro *m, siz i = end; } - texisplice(p, val, strlen(val), pos); + texisplice(p, val, strlen(val), *pos); for (i = 0; i < asz; i++) free(args[i]); @@ -554,7 +592,7 @@ parseword(struct texi *p, size_t *pos, char extra) * index after the command name. */ enum texicmd -texicmd(struct texi *p, size_t pos, size_t *end, struct teximacro **macro) +texicmd(const struct texi *p, size_t pos, size_t *end, struct teximacro **macro) { size_t i, len, toksz; @@ -569,7 +607,7 @@ texicmd(struct texi *p, size_t pos, size_t *end, struc return(TEXICMD__MAX); /* Alphabetic commands are special. */ - if ( ! isalpha(BUF(p)[pos])) { + if ( ! isalpha((unsigned char)BUF(p)[pos])) { if ((*end = pos + 1) == BUFSZ(p)) return(TEXICMD__MAX); for (i = 0; i < TEXICMD__MAX; i++) { @@ -633,7 +671,7 @@ texicmd(struct texi *p, size_t pos, size_t *end, struc int parsearg(struct texi *p, size_t *pos, size_t num) { - size_t end; + size_t end, sv; enum texicmd cmd; struct teximacro *macro; @@ -664,10 +702,11 @@ parsearg(struct texi *p, size_t *pos, size_t num) continue; } + sv = p->files[p->filepos - 1].insplice; cmd = texicmd(p, *pos, &end, ¯o); advanceto(p, pos, end); if (NULL != macro) - texiexecmacro(p, macro, pos); + texiexecmacro(p, macro, sv, pos); if (TEXICMD__MAX == cmd) continue; if (NULL != texitoks[cmd].fp) @@ -681,9 +720,9 @@ parsearg(struct texi *p, size_t *pos, size_t num) * This will stop in the event of EOF or if we're not at a bracket. */ void -parsebracket(struct texi *p, size_t *pos) +parsebracket(struct texi *p, size_t *pos, int dostack) { - size_t end; + size_t end, sv, stack; enum texicmd cmd; struct teximacro *macro; @@ -694,12 +733,25 @@ parsebracket(struct texi *p, size_t *pos) return; advance(p, pos); + stack = 0; while ((*pos = advancenext(p, pos)) < BUFSZ(p)) { switch (BUF(p)[*pos]) { case ('}'): + if (stack > 0) { + stack--; + advance(p, pos); + texiputchar(p, '}'); + continue; + } advance(p, pos); return; case ('{'): + if (dostack) { + stack++; + advance(p, pos); + texiputchar(p, '{'); + continue; + } if (0 == p->ign) texiwarn(p, "unexpected \"{\""); advance(p, pos); @@ -711,10 +763,11 @@ parsebracket(struct texi *p, size_t *pos) continue; } + sv = p->files[p->filepos - 1].insplice; cmd = texicmd(p, *pos, &end, ¯o); advanceto(p, pos, end); if (NULL != macro) - texiexecmacro(p, macro, pos); + texiexecmacro(p, macro, sv, pos); if (TEXICMD__MAX == cmd) continue; if (NULL != texitoks[cmd].fp) @@ -730,7 +783,7 @@ parsebracket(struct texi *p, size_t *pos) void parseeoln(struct texi *p, size_t *pos) { - size_t end; + size_t end, sv; enum texicmd cmd; struct teximacro *macro; @@ -759,10 +812,11 @@ parseeoln(struct texi *p, size_t *pos) continue; } + sv = p->files[p->filepos - 1].insplice; cmd = texicmd(p, *pos, &end, ¯o); advanceto(p, pos, end); if (NULL != macro) - texiexecmacro(p, macro, pos); + texiexecmacro(p, macro, sv, pos); if (TEXICMD__MAX == cmd) continue; if (NULL != texitoks[cmd].fp) @@ -774,13 +828,30 @@ parseeoln(struct texi *p, size_t *pos) } /* + * Peek to see if there's a command after subsequent whitespace. + * If so, return the macro identifier. + * This DOES NOT work with user-defined macros. + */ +enum texicmd +peekcmd(const struct texi *p, size_t pos) +{ + size_t end; + + while (pos < BUFSZ(p) && ismspace(BUF(p)[pos])) + pos++; + if (pos == BUFSZ(p) || '@' != BUF(p)[pos]) + return(TEXICMD__MAX); + return(texicmd(p, pos, &end, NULL)); +} + +/* * Parse a single word or command. * This will return immediately at the EOF. */ static void parsesingle(struct texi *p, size_t *pos) { - size_t end; + size_t end, sv; enum texicmd cmd; struct teximacro *macro; @@ -805,10 +876,11 @@ parsesingle(struct texi *p, size_t *pos) return; } + sv = p->files[p->filepos - 1].insplice; cmd = texicmd(p, *pos, &end, ¯o); advanceto(p, pos, end); if (NULL != macro) - texiexecmacro(p, macro, pos); + texiexecmacro(p, macro, sv, pos); if (TEXICMD__MAX == cmd) return; if (NULL != texitoks[cmd].fp) @@ -833,7 +905,7 @@ parselinearg(struct texi *p, size_t *pos) } if (*pos < BUFSZ(p) && '{' == BUF(p)[*pos]) - parsebracket(p, pos); + parsebracket(p, pos, 0); else if (*pos < BUFSZ(p) && '\n' != BUF(p)[*pos]) parsesingle(p, pos); else @@ -855,7 +927,7 @@ parseeof(struct texi *p) } void -texisplice(struct texi *p, const char *buf, size_t sz, size_t *pos) +texisplice(struct texi *p, const char *buf, size_t sz, size_t pos) { char *cp; struct texifile *f; @@ -871,8 +943,9 @@ texisplice(struct texi *p, const char *buf, size_t sz, f->map = cp; } - memmove(f->map + *pos + sz, f->map + *pos, f->mapsz - *pos); - memcpy(f->map + *pos, buf, sz); + f->insplice += sz; + memmove(f->map + pos + sz, f->map + pos, f->mapsz - pos); + memcpy(f->map + pos, buf, sz); f->mapsz += sz; } @@ -884,7 +957,7 @@ texisplice(struct texi *p, const char *buf, size_t sz, void parseto(struct texi *p, size_t *pos, const char *endtoken) { - size_t end; + size_t end, sv; enum texicmd cmd; size_t endtoksz; struct teximacro *macro; @@ -911,6 +984,7 @@ parseto(struct texi *p, size_t *pos, const char *endto continue; } + sv = p->files[p->filepos - 1].insplice; cmd = texicmd(p, *pos, &end, ¯o); advanceto(p, pos, end); if (TEXICMD_END == cmd) { @@ -931,7 +1005,7 @@ parseto(struct texi *p, size_t *pos, const char *endto continue; } if (NULL != macro) - texiexecmacro(p, macro, pos); + texiexecmacro(p, macro, sv, pos); if (TEXICMD__MAX == cmd) continue; if (NULL != texitoks[cmd].fp) @@ -1188,6 +1262,9 @@ argparse(struct texi *p, size_t *pos, size_t *argsz, s args = NULL; *argsz = 0; + if (*pos == BUFSZ(p)) + return(args); + if ('{' != BUF(p)[*pos] && hint) { /* * Special case: if we encounter an unbracketed argument @@ -1214,6 +1291,8 @@ argparse(struct texi *p, size_t *pos, size_t *argsz, s } else if ('{' != BUF(p)[*pos]) return(args); + assert('{' == BUF(p)[*pos]); + /* Parse til the closing '}', putting into the array. */ advance(p, pos); while (*pos < BUFSZ(p)) { @@ -1270,3 +1349,112 @@ argparse(struct texi *p, size_t *pos, size_t *argsz, s advance(p, pos); return(args); } + +/* + * If we're printing chapters, then do some naviation here and then + * close our outfile. + * I want to call this the SEE ALSO section, but that's not really what + * it is: we'll refer to the "initial" (top) node and the next and + * previous chapters. + */ +void +teximdocclose(struct texi *p, int last) +{ + char buf[PATH_MAX]; + + if (NULL == p->chapters || 0 == p->chapnum) + return; + + teximacro(p, "Sh INFO NAVIGATION"); + + /* Print a reference to the "top" node. */ + if (p->chapnum > 1) { + snprintf(buf, sizeof(buf), "node1 7"); + teximacroopen(p, "Xr "); + texiputchars(p, buf); + texiputchars(p, " ,"); + teximacroclose(p); + } + + /* Print a reference to the previous node. */ + if (p->chapnum > 2) { + snprintf(buf, sizeof(buf), + "node%zu 7", p->chapnum - 1); + teximacroopen(p, "Xr "); + texiputchars(p, buf); + if ( ! last) + texiputchars(p, " ,"); + teximacroclose(p); + } + + /* Print a reference to the next node. */ + if ( ! last) { + snprintf(buf, sizeof(buf), + "node%zu 7", p->chapnum + 1); + teximacroopen(p, "Xr "); + texiputchars(p, buf); + teximacroclose(p); + } + + fclose(p->outfile); +} + +/* + * Open a mdoc(7) context. + * If we're printing chapters, then open the outfile here, too. + * Otherwise just print the mdoc(7) prologue. + */ +void +teximdocopen(struct texi *p, size_t *pos) +{ + const char *cp; + time_t t; + char date[32]; + char fname[PATH_MAX]; + + if (NULL != p->chapters) { + snprintf(fname, sizeof(fname), "%s/node%zu.7", + p->chapters, ++p->chapnum); + p->outfile = fopen(fname, "w"); + if (NULL == p->outfile) + texiabort(p, fname); + } + + /* + * Here we print our standard mdoc(7) prologue. + * We use the title set with @settitle for the `Nd' description + * and the source document filename (the first one as invoked on + * the command line) for the title. + * The date is set to the current date. + */ + t = time(NULL); + strftime(date, sizeof(date), "%F", localtime(&t)); + + teximacroopen(p, "Dd"); + texiputchars(p, date); + teximacroclose(p); + teximacroopen(p, "Dt"); + for (cp = p->title; '\0' != *cp; cp++) + texiputchar(p, toupper((unsigned int)*cp)); + texiputchars(p, " 7"); + teximacroclose(p); + teximacro(p, "Os"); + teximacro(p, "Sh NAME"); + teximacroopen(p, "Nm"); + for (cp = p->title; '\0' != *cp; cp++) + texiputchar(p, *cp); + teximacroclose(p); + teximacroopen(p, "Nd"); + /* + * The subtitle `Nd' can consist of arbitrary macros, so paste + * it and parse to the end of the line. + */ + if (NULL != p->subtitle) { + texisplice(p, p->subtitle, strlen(p->subtitle), *pos); + parseeoln(p, pos); + } else + texiputchars(p, "Unknown description"); + teximacroclose(p); + p->seenvs = 1; +} +