From 589e7c1db2b008d569817318dd8d8a990d926686 Mon Sep 17 00:00:00 2001 From: Sascha Wildner Date: Tue, 27 Oct 2009 09:42:29 +0100 Subject: [PATCH] Bring in OpenBSD's mandoc(1) tool for formatting manual pages. It's not (yet) used by anything but it is quite useful for error checking. Thanks to Kristaps Dzonsons and OpenBSD! --- usr.bin/Makefile | 1 + usr.bin/mandoc/Makefile | 17 + usr.bin/mandoc/arch.c | 32 + usr.bin/mandoc/arch.in | 52 + usr.bin/mandoc/att.c | 32 + usr.bin/mandoc/att.in | 37 + usr.bin/mandoc/chars.c | 204 +++ usr.bin/mandoc/chars.h | 34 + usr.bin/mandoc/chars.in | 418 ++++++ usr.bin/mandoc/html.c | 648 ++++++++++ usr.bin/mandoc/html.h | 133 ++ usr.bin/mandoc/lib.c | 32 + usr.bin/mandoc/lib.in | 57 + usr.bin/mandoc/libman.h | 116 ++ usr.bin/mandoc/libmandoc.h | 26 + usr.bin/mandoc/libmdoc.h | 188 +++ usr.bin/mandoc/main.c | 662 ++++++++++ usr.bin/mandoc/main.h | 47 + usr.bin/mandoc/man.3 | 283 ++++ usr.bin/mandoc/man.7 | 589 +++++++++ usr.bin/mandoc/man.c | 628 +++++++++ usr.bin/mandoc/man.h | 116 ++ usr.bin/mandoc/man_action.c | 219 ++++ usr.bin/mandoc/man_argv.c | 98 ++ usr.bin/mandoc/man_hash.c | 77 ++ usr.bin/mandoc/man_html.c | 696 ++++++++++ usr.bin/mandoc/man_macro.c | 374 ++++++ usr.bin/mandoc/man_term.c | 986 ++++++++++++++ usr.bin/mandoc/man_validate.c | 280 ++++ usr.bin/mandoc/mandoc.1 | 416 ++++++ usr.bin/mandoc/mandoc.c | 104 ++ usr.bin/mandoc/mandoc_char.7 | 616 +++++++++ usr.bin/mandoc/manuals.7 | 236 ++++ usr.bin/mandoc/mdoc.3 | 336 +++++ usr.bin/mandoc/mdoc.7 | 1775 +++++++++++++++++++++++++ usr.bin/mandoc/mdoc.c | 749 +++++++++++ usr.bin/mandoc/mdoc.h | 304 +++++ usr.bin/mandoc/mdoc_action.c | 980 ++++++++++++++ usr.bin/mandoc/mdoc_argv.c | 771 +++++++++++ usr.bin/mandoc/mdoc_hash.c | 88 ++ usr.bin/mandoc/mdoc_html.c | 2217 ++++++++++++++++++++++++++++++++ usr.bin/mandoc/mdoc_macro.c | 1350 +++++++++++++++++++ usr.bin/mandoc/mdoc_strings.c | 237 ++++ usr.bin/mandoc/mdoc_term.c | 2067 +++++++++++++++++++++++++++++ usr.bin/mandoc/mdoc_validate.c | 1307 +++++++++++++++++++ usr.bin/mandoc/msec.c | 32 + usr.bin/mandoc/msec.in | 40 + usr.bin/mandoc/out.c | 119 ++ usr.bin/mandoc/out.h | 58 + usr.bin/mandoc/st.c | 32 + usr.bin/mandoc/st.in | 68 + usr.bin/mandoc/term.c | 633 +++++++++ usr.bin/mandoc/term.h | 61 + usr.bin/mandoc/tree.c | 208 +++ usr.bin/mandoc/vol.c | 32 + usr.bin/mandoc/vol.in | 35 + 56 files changed, 21953 insertions(+) create mode 100644 usr.bin/mandoc/Makefile create mode 100644 usr.bin/mandoc/arch.c create mode 100644 usr.bin/mandoc/arch.in create mode 100644 usr.bin/mandoc/att.c create mode 100644 usr.bin/mandoc/att.in create mode 100644 usr.bin/mandoc/chars.c create mode 100644 usr.bin/mandoc/chars.h create mode 100644 usr.bin/mandoc/chars.in create mode 100644 usr.bin/mandoc/html.c create mode 100644 usr.bin/mandoc/html.h create mode 100644 usr.bin/mandoc/lib.c create mode 100644 usr.bin/mandoc/lib.in create mode 100644 usr.bin/mandoc/libman.h create mode 100644 usr.bin/mandoc/libmandoc.h create mode 100644 usr.bin/mandoc/libmdoc.h create mode 100644 usr.bin/mandoc/main.c create mode 100644 usr.bin/mandoc/main.h create mode 100644 usr.bin/mandoc/man.3 create mode 100644 usr.bin/mandoc/man.7 create mode 100644 usr.bin/mandoc/man.c create mode 100644 usr.bin/mandoc/man.h create mode 100644 usr.bin/mandoc/man_action.c create mode 100644 usr.bin/mandoc/man_argv.c create mode 100644 usr.bin/mandoc/man_hash.c create mode 100644 usr.bin/mandoc/man_html.c create mode 100644 usr.bin/mandoc/man_macro.c create mode 100644 usr.bin/mandoc/man_term.c create mode 100644 usr.bin/mandoc/man_validate.c create mode 100644 usr.bin/mandoc/mandoc.1 create mode 100644 usr.bin/mandoc/mandoc.c create mode 100644 usr.bin/mandoc/mandoc_char.7 create mode 100644 usr.bin/mandoc/manuals.7 create mode 100644 usr.bin/mandoc/mdoc.3 create mode 100644 usr.bin/mandoc/mdoc.7 create mode 100644 usr.bin/mandoc/mdoc.c create mode 100644 usr.bin/mandoc/mdoc.h create mode 100644 usr.bin/mandoc/mdoc_action.c create mode 100644 usr.bin/mandoc/mdoc_argv.c create mode 100644 usr.bin/mandoc/mdoc_hash.c create mode 100644 usr.bin/mandoc/mdoc_html.c create mode 100644 usr.bin/mandoc/mdoc_macro.c create mode 100644 usr.bin/mandoc/mdoc_strings.c create mode 100644 usr.bin/mandoc/mdoc_term.c create mode 100644 usr.bin/mandoc/mdoc_validate.c create mode 100644 usr.bin/mandoc/msec.c create mode 100644 usr.bin/mandoc/msec.in create mode 100644 usr.bin/mandoc/out.c create mode 100644 usr.bin/mandoc/out.h create mode 100644 usr.bin/mandoc/st.c create mode 100644 usr.bin/mandoc/st.in create mode 100644 usr.bin/mandoc/term.c create mode 100644 usr.bin/mandoc/term.h create mode 100644 usr.bin/mandoc/tree.c create mode 100644 usr.bin/mandoc/vol.c create mode 100644 usr.bin/mandoc/vol.in diff --git a/usr.bin/Makefile b/usr.bin/Makefile index c19ca50920..04a3630e07 100644 --- a/usr.bin/Makefile +++ b/usr.bin/Makefile @@ -109,6 +109,7 @@ SUBDIR= alias \ m4 \ mail \ make \ + mandoc \ mesg \ mkdep \ mkfifo \ diff --git a/usr.bin/mandoc/Makefile b/usr.bin/mandoc/Makefile new file mode 100644 index 0000000000..46882b33b0 --- /dev/null +++ b/usr.bin/mandoc/Makefile @@ -0,0 +1,17 @@ +# $OpenBSD: Makefile,v 1.19 2009/10/19 09:16:58 schwarze Exp $ + +VERSION=1.9.9 +CFLAGS+=-DVERSION=\"${VERSION}\" +WARNS?= 3 + +SRCS= mandoc.c mdoc_macro.c mdoc.c mdoc_hash.c mdoc_strings.c \ + mdoc_argv.c mdoc_validate.c mdoc_action.c lib.c att.c \ + arch.c vol.c msec.c st.c +SRCS+= man_macro.c man.c man_hash.c man_validate.c \ + man_action.c man_argv.c +SRCS+= main.c mdoc_term.c chars.c term.c tree.c man_term.c +SRCS+= html.c mdoc_html.c man_html.c out.c + +PROG= mandoc + +.include diff --git a/usr.bin/mandoc/arch.c b/usr.bin/mandoc/arch.c new file mode 100644 index 0000000000..0459e1fefd --- /dev/null +++ b/usr.bin/mandoc/arch.c @@ -0,0 +1,32 @@ +/* $Id: arch.c,v 1.2 2009/06/14 23:00:57 schwarze Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include + +#include "libmdoc.h" + +#define LINE(x, y) \ + if (0 == strcmp(p, x)) return(y); + +const char * +mdoc_a2arch(const char *p) +{ + +#include "arch.in" + + return(NULL); +} diff --git a/usr.bin/mandoc/arch.in b/usr.bin/mandoc/arch.in new file mode 100644 index 0000000000..b1c23eea38 --- /dev/null +++ b/usr.bin/mandoc/arch.in @@ -0,0 +1,52 @@ +/* $Id: arch.in,v 1.2 2009/06/14 23:00:57 schwarze Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This file defines the architecture token of the .Dt prologue macro. + * All architectures that your system supports (or the manuals of your + * system) should be included here. The right-hand-side is the + * formatted output. + * + * Be sure to escape strings. + */ + +LINE("alpha", "Alpha") +LINE("amd64", "AMD64") +LINE("amiga", "Amiga") +LINE("arc", "ARC") +LINE("arm", "ARM") +LINE("armish", "ARMISH") +LINE("aviion", "AViiON") +LINE("hp300", "HP300") +LINE("hppa", "HPPA") +LINE("hppa64", "HPPA64") +LINE("i386", "i386") +LINE("landisk", "LANDISK") +LINE("luna88k", "Luna88k") +LINE("mac68k", "Mac68k") +LINE("macppc", "MacPPC") +LINE("mvme68k", "MVME68k") +LINE("mvme88k", "MVME88k") +LINE("mvmeppc", "MVMEPPC") +LINE("pmax", "PMAX") +LINE("sgi", "SGI") +LINE("socppc", "SOCPPC") +LINE("sparc", "SPARC") +LINE("sparc64", "SPARC64") +LINE("sun3", "Sun3") +LINE("vax", "VAX") +LINE("zaurus", "Zaurus") diff --git a/usr.bin/mandoc/att.c b/usr.bin/mandoc/att.c new file mode 100644 index 0000000000..c3020c6b69 --- /dev/null +++ b/usr.bin/mandoc/att.c @@ -0,0 +1,32 @@ +/* $Id: att.c,v 1.2 2009/06/14 23:00:57 schwarze Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include + +#include "libmdoc.h" + +#define LINE(x, y) \ + if (0 == strcmp(p, x)) return(y); + +const char * +mdoc_a2att(const char *p) +{ + +#include "att.in" + + return(NULL); +} diff --git a/usr.bin/mandoc/att.in b/usr.bin/mandoc/att.in new file mode 100644 index 0000000000..c0efe3d5b3 --- /dev/null +++ b/usr.bin/mandoc/att.in @@ -0,0 +1,37 @@ +/* $Id: att.in,v 1.2 2009/06/14 23:00:57 schwarze Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This file defines the AT&T versions of the .At macro. This probably + * isn't going to change. The right-hand side is the formatted string. + * + * Be sure to escape strings. + */ + +LINE("v1", "Version 1 AT&T UNIX") +LINE("v2", "Version 2 AT&T UNIX") +LINE("v3", "Version 3 AT&T UNIX") +LINE("v4", "Version 4 AT&T UNIX") +LINE("v5", "Version 5 AT&T UNIX") +LINE("v6", "Version 6 AT&T UNIX") +LINE("v7", "Version 7 AT&T UNIX") +LINE("32v", "Version 32V AT&T UNIX") +LINE("V", "AT&T System V UNIX") +LINE("V.1", "AT&T System V.1 UNIX") +LINE("V.2", "AT&T System V.2 UNIX") +LINE("V.3", "AT&T System V.3 UNIX") +LINE("V.4", "AT&T System V.4 UNIX") diff --git a/usr.bin/mandoc/chars.c b/usr.bin/mandoc/chars.c new file mode 100644 index 0000000000..1190fda53d --- /dev/null +++ b/usr.bin/mandoc/chars.c @@ -0,0 +1,204 @@ +/* $Id: chars.c,v 1.2 2009/10/19 09:56:35 schwarze Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include +#include +#include + +#include "chars.h" + +#define PRINT_HI 126 +#define PRINT_LO 32 + +struct ln { + struct ln *next; + const char *code; + const char *ascii; + const char *html; + size_t codesz; + size_t asciisz; + size_t htmlsz; + int type; +#define CHARS_CHAR (1 << 0) +#define CHARS_STRING (1 << 1) +#define CHARS_BOTH (0x03) +}; + +#define LINES_MAX 351 + +#define CHAR(w, x, y, z, a, b) \ + { NULL, (w), (y), (a), (x), (z), (b), CHARS_CHAR }, +#define STRING(w, x, y, z, a, b) \ + { NULL, (w), (y), (a), (x), (z), (b), CHARS_STRING }, +#define BOTH(w, x, y, z, a, b) \ + { NULL, (w), (y), (a), (x), (z), (b), CHARS_BOTH }, + +static struct ln lines[LINES_MAX] = { +#include "chars.in" +}; + +struct tbl { + enum chars type; + struct ln **htab; +}; + +static inline int match(const struct ln *, + const char *, size_t, int); +static const char *find(struct tbl *, const char *, + size_t, size_t *, int); + + +void +chars_free(void *arg) +{ + struct tbl *tab; + + tab = (struct tbl *)arg; + + free(tab->htab); + free(tab); +} + + +void * +chars_init(enum chars type) +{ + struct tbl *tab; + struct ln **htab; + struct ln *pp; + int i, hash; + + /* + * Constructs a very basic chaining hashtable. The hash routine + * is simply the integral value of the first character. + * Subsequent entries are chained in the order they're processed + * (they're in-line re-ordered during lookup). + */ + + if (NULL == (tab = malloc(sizeof(struct tbl)))) + err(1, "malloc"); + tab->type = type; + + htab = calloc(PRINT_HI - PRINT_LO + 1, sizeof(struct ln **)); + if (NULL == htab) + err(1, "malloc"); + + for (i = 0; i < LINES_MAX; i++) { + hash = (int)lines[i].code[0] - PRINT_LO; + + if (NULL == (pp = htab[hash])) { + htab[hash] = &lines[i]; + continue; + } + + for ( ; pp->next; pp = pp->next) + /* Scan ahead. */ ; + pp->next = &lines[i]; + } + + tab->htab = htab; + return(tab); +} + + +const char * +chars_a2ascii(void *arg, const char *p, size_t sz, size_t *rsz) +{ + + return(find((struct tbl *)arg, p, sz, rsz, CHARS_CHAR)); +} + + +const char * +chars_a2res(void *arg, const char *p, size_t sz, size_t *rsz) +{ + + return(find((struct tbl *)arg, p, sz, rsz, CHARS_STRING)); +} + + +static const char * +find(struct tbl *tab, const char *p, size_t sz, size_t *rsz, int type) +{ + struct ln *pp, *prev; + struct ln **htab; + int hash; + + assert(p); + assert(sz > 0); + + if (p[0] < PRINT_LO || p[0] > PRINT_HI) + return(NULL); + + /* + * Lookup the symbol in the symbol hash. See ascii2htab for the + * hashtable specs. This dynamically re-orders the hash chain + * to optimise for repeat hits. + */ + + hash = (int)p[0] - PRINT_LO; + htab = tab->htab; + + if (NULL == (pp = htab[hash])) + return(NULL); + + if (NULL == pp->next) { + if ( ! match(pp, p, sz, type)) + return(NULL); + + if (CHARS_HTML == tab->type) { + *rsz = pp->htmlsz; + return(pp->html); + } + *rsz = pp->asciisz; + return(pp->ascii); + } + + for (prev = NULL; pp; pp = pp->next) { + if ( ! match(pp, p, sz, type)) { + prev = pp; + continue; + } + + if (prev) { + prev->next = pp->next; + pp->next = htab[hash]; + htab[hash] = pp; + } + + if (CHARS_HTML == tab->type) { + *rsz = pp->htmlsz; + return(pp->html); + } + *rsz = pp->asciisz; + return(pp->ascii); + } + + return(NULL); +} + + +static inline int +match(const struct ln *ln, const char *p, size_t sz, int type) +{ + + if ( ! (ln->type & type)) + return(0); + if (ln->codesz != sz) + return(0); + return(0 == strncmp(ln->code, p, sz)); +} diff --git a/usr.bin/mandoc/chars.h b/usr.bin/mandoc/chars.h new file mode 100644 index 0000000000..fa1608a3b5 --- /dev/null +++ b/usr.bin/mandoc/chars.h @@ -0,0 +1,34 @@ +/* $Id: chars.h,v 1.1 2009/10/19 09:16:58 schwarze Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef CHARS_H +#define CHARS_H + +__BEGIN_DECLS + +enum chars { + CHARS_ASCII, + CHARS_HTML +}; + +void *chars_init(enum chars); +const char *chars_a2ascii(void *, const char *, size_t, size_t *); +const char *chars_a2res(void *, const char *, size_t, size_t *); +void chars_free(void *); + +__END_DECLS + +#endif /*!CHARS_H*/ diff --git a/usr.bin/mandoc/chars.in b/usr.bin/mandoc/chars.in new file mode 100644 index 0000000000..2f4bc0d025 --- /dev/null +++ b/usr.bin/mandoc/chars.in @@ -0,0 +1,418 @@ +/* $Id: chars.in,v 1.2 2009/10/19 09:56:35 schwarze Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * The ASCII translation tables. STRING corresponds to predefined + * strings (cf. mdoc_samples.7 and tmac/mdoc/doc-nroff). CHAR + * corresponds to special characters (cf. groff_char.7). BOTH contains + * sequences that are equivalent in both STRING and CHAR. + * + * Either way, the left-hand side corresponds to the input sequence (\x, + * \(xx, \*(xx and so on) whose length is listed second element. The + * right-hand side is what's produced by the front-end, with the fourth + * element being its length. + * + * XXX - C-escape strings! + * XXX - update LINES_MAX if adding more! + */ + +/* Spacing. */ +CHAR("c", 1, "", 0, "", 0) +CHAR("0", 1, " ", 1, " ", 7) +CHAR(" ", 1, " ", 1, " ", 7) +CHAR("~", 1, " ", 1, " ", 6) +CHAR("%", 1, "", 0, "", 0) +CHAR("&", 1, "", 0, "", 0) +CHAR("^", 1, "", 0, "", 0) +CHAR("|", 1, "", 0, "", 0) + +/* Accents. */ +CHAR("a\"", 2, "\"", 1, "̋", 6) +CHAR("a-", 2, "-", 1, "¯", 6) +CHAR("a.", 2, ".", 1, "˙", 6) +CHAR("a^", 2, "^", 1, "̂", 6) +CHAR("\'", 1, "\'", 1, "́", 6) +BOTH("aa", 2, "\'", 1, "́", 6) +BOTH("ga", 2, "`", 1, "̀", 6) +CHAR("`", 1, "`", 1, "̀", 6) +CHAR("ab", 2, "`", 1, "̆", 6) +CHAR("ac", 2, ",", 1, "̧", 6) +CHAR("ad", 2, "\"", 1, "̈", 6) +CHAR("ah", 2, "v", 1, "ˇ", 6) +CHAR("ao", 2, "o", 1, "˚", 6) +CHAR("a~", 2, "~", 1, "̃", 6) +CHAR("ho", 2, ",", 1, "̨", 6) +CHAR("ha", 2, "^", 1, "^", 1) +CHAR("ti", 2, "~", 1, "~", 1) + +/* Quotes. */ +CHAR("Bq", 2, ",,", 2, "„", 7) +CHAR("bq", 2, ",", 1, "‚", 7) +BOTH("lq", 2, "``", 2, "“", 7) +BOTH("rq", 2, "\'\'", 2, "”", 7) +CHAR("oq", 2, "`", 1, "‘", 7) +CHAR("cq", 2, "\'", 1, "’", 7) +CHAR("aq", 2, "\'", 1, "\'", 1) +CHAR("dq", 2, "\"", 1, "\"", 1) +CHAR("Fo", 2, "<<", 2, "«", 6) +CHAR("Fc", 2, ">>", 2, "»", 6) +CHAR("fo", 2, "<", 1, "‹", 7) +CHAR("fc", 2, ">", 1, "›", 7) + +/* Brackets. */ +CHAR("lB", 2, "[", 1, "[", 1) +CHAR("rB", 2, "]", 1, "]", 1) +CHAR("lC", 2, "{", 1, "{", 1) +CHAR("rC", 2, "}", 1, "}", 1) +CHAR("la", 2, "<", 1, "⟨", 8) +CHAR("ra", 2, ">", 1, "⟩", 8) +CHAR("bv", 2, "|", 1, "⎪", 7) +CHAR("braceex", 7, "|", 1, "⎪", 7) +CHAR("bracketlefttp", 13, "|", 1, "⎡", 7) +CHAR("bracketleftbp", 13, "|", 1, "⎣", 7) +CHAR("bracketleftex", 13, "|", 1, "⎢", 7) +CHAR("bracketrighttp", 14, "|", 1, "⎤", 7) +CHAR("bracketrightbp", 14, "|", 1, "⎦", 7) +CHAR("bracketrightex", 14, "|", 1, "⎥", 7) +CHAR("lt", 2, ",-", 2, "⎧", 7) +CHAR("bracelefttp", 11, ",-", 2, "⎧", 7) +CHAR("lk", 2, "{", 1, "⎨", 7) +CHAR("braceleftmid", 12, "{", 1, "⎨", 7) +CHAR("lb", 2, ",-", 2, "⎩", 7) +CHAR("braceleftbp", 11, "`-", 2, "⎩", 7) +CHAR("braceleftex", 11, "|", 1, "⎪", 7) +CHAR("rt", 2, "-.", 2, "⎫", 7) +CHAR("bracerighttp", 12, "-.", 2, "⎫", 7) +CHAR("rk", 2, "}", 1, "⎬", 7) +CHAR("bracerightmid", 13, "}", 1, "⎬", 7) +CHAR("rb", 2, "-\'", 2, "⎭", 7) +CHAR("bracerightbp", 12, "-\'", 2, "⎭", 7) +CHAR("bracerightex", 12, "|", 1, "⎪", 7) +CHAR("parenlefttp", 11, "/", 1, "⎛", 7) +CHAR("parenleftbp", 11, "\\", 1, "⎝", 7) +CHAR("parenleftex", 11, "|", 1, "⎜", 7) +CHAR("parenrighttp", 12, "\\", 1, "⎞", 7) +CHAR("parenrightbp", 12, "/", 1, "⎠", 7) +CHAR("parenrightex", 12, "|", 1, "⎟", 7) + +/* Greek characters. */ +CHAR("*A", 2, "A", 1, "Α", 6) +CHAR("*B", 2, "B", 1, "Β", 6) +CHAR("*G", 2, "|", 1, "Γ", 6) +CHAR("*D", 2, "/\\", 2, "Δ", 6) +CHAR("*E", 2, "E", 1, "Ε", 6) +CHAR("*Z", 2, "Z", 1, "Ζ", 6) +CHAR("*Y", 2, "H", 1, "Η", 6) +CHAR("*H", 2, "O", 1, "Θ", 6) +CHAR("*I", 2, "I", 1, "Ι", 6) +CHAR("*K", 2, "K", 1, "Κ", 6) +CHAR("*L", 2, "/\\", 2, "Λ", 6) +CHAR("*M", 2, "M", 1, "Μ", 6) +CHAR("*N", 2, "N", 1, "Ν", 6) +CHAR("*C", 2, "H", 1, "Ξ", 6) +CHAR("*O", 2, "O", 1, "Ο", 6) +CHAR("*P", 2, "TT", 2, "Π", 6) +CHAR("*R", 2, "P", 1, "Ρ", 6) +CHAR("*S", 2, ">", 1, "Σ", 6) +CHAR("*T", 2, "T", 1, "Τ", 6) +CHAR("*U", 2, "Y", 1, "Υ", 6) +CHAR("*F", 2, "O_", 1, "Φ", 6) +CHAR("*X", 2, "X", 1, "Χ", 6) +CHAR("*Q", 2, "Y", 1, "Ψ", 6) +CHAR("*W", 2, "O", 1, "Ω", 6) +CHAR("*a", 2, "a", 1, "α", 6) +CHAR("*b", 2, "B", 1, "β", 6) +CHAR("*g", 2, "y", 1, "γ", 6) +CHAR("*d", 2, "d", 1, "δ", 6) +CHAR("*e", 2, "e", 1, "ε", 6) +CHAR("*z", 2, "C", 1, "ζ", 6) +CHAR("*y", 2, "n", 1, "η", 6) +CHAR("*h", 2, "0", 1, "θ", 6) +CHAR("*i", 2, "i", 1, "ι", 6) +CHAR("*k", 2, "k", 1, "κ", 6) +CHAR("*l", 2, "\\", 1, "λ", 6) +CHAR("*m", 2, "u", 1, "μ", 6) +CHAR("*n", 2, "v", 1, "ν", 6) +CHAR("*c", 2, "E", 1, "ξ", 6) +CHAR("*o", 2, "o", 1, "ο", 6) +CHAR("*p", 2, "n", 1, "π", 6) +CHAR("*r", 2, "p", 1, "ρ", 6) +CHAR("*s", 2, "o", 1, "σ", 6) +CHAR("*t", 2, "t", 1, "τ", 6) +CHAR("*u", 2, "u", 1, "υ", 6) +CHAR("*f", 2, "o", 1, "ϕ", 6) +CHAR("*x", 2, "x", 1, "χ", 6) +CHAR("*q", 2, "u", 1, "ψ", 6) +CHAR("*w", 2, "w", 1, "ω", 6) +CHAR("+h", 2, "0", 1, "ϑ", 6) +CHAR("+f", 2, "o", 1, "φ", 6) +CHAR("+p", 2, "w", 1, "ϖ", 6) +CHAR("+e", 2, "e", 1, "ϵ", 7) +CHAR("ts", 2, "s", 1, "ς", 6) + +/* Accented letters. */ +CHAR(",C", 2, "C", 1, "Ç", 6) +CHAR(",c", 2, "c", 1, "ç", 6) +CHAR("/L", 2, "L", 1, "Ł", 6) +CHAR("/O", 2, "O", 1, "Ø", 6) +CHAR("/l", 2, "l", 1, "ł", 6) +CHAR("/o", 2, "o", 1, "ø", 6) +CHAR("oA", 2, "A", 1, "Å", 6) +CHAR("oa", 2, "a", 1, "å", 6) +CHAR(":A", 2, "A", 1, "Ä", 6) +CHAR(":E", 2, "E", 1, "Ë", 6) +CHAR(":I", 2, "I", 1, "Ï", 6) +CHAR(":O", 2, "O", 1, "Ö", 6) +CHAR(":U", 2, "U", 1, "Ü", 6) +CHAR(":a", 2, "a", 1, "ä", 6) +CHAR(":e", 2, "e", 1, "ë", 6) +CHAR(":i", 2, "i", 1, "ï", 6) +CHAR(":o", 2, "o", 1, "õ", 6) +CHAR(":u", 2, "u", 1, "ü", 6) +CHAR(":y", 2, "y", 1, "ÿ", 6) +CHAR("\'A", 2, "A", 1, "Á", 6) +CHAR("\'E", 2, "E", 1, "É", 6) +CHAR("\'I", 2, "I", 1, "Í", 6) +CHAR("\'O", 2, "O", 1, "Ó", 6) +CHAR("\'U", 2, "U", 1, "Ú", 6) +CHAR("\'a", 2, "a", 1, "á", 6) +CHAR("\'e", 2, "e", 1, "é", 6) +CHAR("\'i", 2, "i", 1, "í", 6) +CHAR("\'o", 2, "o", 1, "ó", 6) +CHAR("\'u", 2, "u", 1, "ú", 6) +CHAR("^A", 2, "A", 1, "Â", 6) +CHAR("^E", 2, "E", 1, "Ê", 6) +CHAR("^I", 2, "I", 1, "Î", 6) +CHAR("^O", 2, "O", 1, "Ô", 6) +CHAR("^U", 2, "U", 1, "Û", 6) +CHAR("^a", 2, "a", 1, "â", 6) +CHAR("^e", 2, "e", 1, "ê", 6) +CHAR("^i", 2, "i", 1, "î", 6) +CHAR("^o", 2, "o", 1, "ô", 6) +CHAR("^u", 2, "u", 1, "û", 6) +CHAR("`A", 2, "A", 1, "À", 6) +CHAR("`E", 2, "E", 1, "È", 6) +CHAR("`I", 2, "I", 1, "Ì", 6) +CHAR("`O", 2, "O", 1, "Ò", 6) +CHAR("`U", 2, "U", 1, "Ù", 6) +CHAR("`a", 2, "a", 1, "à", 6) +CHAR("`e", 2, "e", 1, "è", 6) +CHAR("`i", 2, "i", 1, "ì", 6) +CHAR("`o", 2, "o", 1, "ò", 6) +CHAR("`u", 2, "u", 1, "ù", 6) +CHAR("~A", 2, "A", 1, "Ã", 6) +CHAR("~N", 2, "N", 1, "Ñ", 6) +CHAR("~O", 2, "O", 1, "Õ", 6) +CHAR("~a", 2, "a", 1, "ã", 6) +CHAR("~n", 2, "n", 1, "ñ", 6) +CHAR("~o", 2, "o", 1, "õ", 6) + +/* Arrows and lines. */ +CHAR("<-", 2, "<-", 2, "←", 7) +CHAR("->", 2, "->", 2, "→", 7) +CHAR("<>", 2, "<>", 2, "↔", 7) +CHAR("da", 2, "v", 1, "↓", 7) +BOTH("ua", 2, "^", 1, "↑", 7) +BOTH("va", 2, "^v", 2, "↕", 7) +CHAR("lA", 2, "<=", 2, "⇐", 7) +CHAR("rA", 2, "=>", 2, "⇒", 7) +CHAR("hA", 2, "<=>", 3, "⇔", 7) +CHAR("dA", 2, "v", 1, "⇓", 7) +CHAR("uA", 2, "^", 1, "⇑", 7) +CHAR("vA", 2, "^=v", 3, "⇕", 7) + +/* Logic. */ +CHAR("AN", 2, "^", 1, "∧", 7) +CHAR("OR", 2, "v", 1, "∨", 7) +CHAR("no", 2, "~", 1, "¬", 6) +CHAR("tno", 3, "~", 1, "¬", 6) +CHAR("te", 2, "3", 1, "∃", 7) +CHAR("fa", 2, "V", 1, "∀", 7) +CHAR("st", 2, "-)", 2, "∋", 7) +CHAR("tf", 2, ".:.", 3, "∴", 7) +CHAR("3d", 2, ".:.", 3, "∴", 7) +CHAR("or", 2, "|", 1, "|", 1) + +/* Mathematicals. */ +CHAR("pl", 2, "+", 1, "+", 5) +CHAR("mi", 2, "-", 1, "−", 7) +CHAR("-", 1, "-", 1, "-", 1) +CHAR("-+", 2, "-+", 2, "∓", 7) +CHAR("+-", 2, "+-", 2, "±", 6) +CHAR("t+-", 3, "+-", 2, "±", 6) +CHAR("pc", 2, ".", 1, "·", 6) +CHAR("md", 2, ".", 1, "⋅", 7) +CHAR("mu", 2, "x", 1, "×", 6) +CHAR("tmu", 3, "x", 1, "×", 6) +CHAR("c*", 2, "x", 1, "⊗", 7) +CHAR("c+", 2, "+", 1, "⊕", 7) +CHAR("di", 2, "-:-", 3, "÷", 6) +CHAR("tdi", 3, "-:-", 3, "÷", 6) +CHAR("f/", 2, "/", 1, "⁄", 7) +CHAR("**", 2, "*", 1, "∗", 7) +BOTH("<=", 2, "<=", 2, "≤", 7) +BOTH(">=", 2, ">=", 2, "≥", 7) +CHAR("<<", 2, "<<", 2, "≪", 7) +CHAR(">>", 2, ">>", 2, "≫", 7) +CHAR("eq", 2, "=", 1, "=", 5) +CHAR("!=", 2, "!=", 2, "≠", 7) +CHAR("==", 2, "==", 2, "≡", 7) +CHAR("ne", 2, "!==", 3, "≢", 7) +CHAR("=~", 2, "=~", 2, "≅", 7) +CHAR("-~", 2, "-~", 2, "≃", 7) +CHAR("ap", 2, "~", 1, "∼", 7) +CHAR("~~", 2, "~~", 2, "≈", 7) +CHAR("~=", 2, "~=", 2, "≌", 7) +CHAR("pt", 2, "oc", 2, "∝", 7) +CHAR("es", 2, "{}", 2, "∅", 7) +CHAR("mo", 2, "E", 1, "∈", 7) +CHAR("nm", 2, "!E", 2, "∉", 7) +CHAR("sb", 2, "(=", 2, "⊂", 7) +CHAR("nb", 2, "(!=", 3, "⊄", 7) +CHAR("sp", 2, "=)", 2, "⊃", 7) +CHAR("nc", 2, "!=)", 3, "⊅", 7) +CHAR("ib", 2, "(=", 2, "⊆", 7) +CHAR("ip", 2, "=)", 2, "⊇", 7) +CHAR("ca", 2, "(^)", 3, "∩", 7) +CHAR("cu", 2, "U", 1, "∪", 7) +CHAR("/_", 2, "/_", 2, "∠", 7) +CHAR("pp", 2, "_|_", 3, "⊥", 7) +CHAR("is", 2, "I", 1, "∫", 7) +CHAR("integral", 8, "I", 1, "∫", 7) +CHAR("sum", 3, "E", 1, "∑", 7) +CHAR("product", 7, "TT", 2, "∏", 7) +CHAR("coproduct", 9, "U", 1, "∐", 7) +CHAR("gr", 2, "V", 1, "∇", 7) +CHAR("sr", 2, "\\/", 2, "√", 7) +CHAR("sqrt", 4, "\\/", 2, "√", 7) +CHAR("lc", 2, "|~", 2, "⌈", 7) +CHAR("rc", 2, "~|", 2, "⌉", 7) +CHAR("lf", 2, "|_", 2, "⌊", 7) +CHAR("rf", 2, "_|", 2, "⌋", 7) +CHAR("if", 2, "oo", 2, "∞", 7) +CHAR("Ah", 2, "N", 1, "ℵ", 7) +CHAR("Im", 2, "I", 1, "ℑ", 7) +CHAR("Re", 2, "R", 1, "ℜ", 7) +CHAR("pd", 2, "a", 1, "∂", 7) +CHAR("-h", 2, "/h", 2, "ℏ", 7) + +/* Ligatures. */ +CHAR("ff", 2, "ff", 2, "ff", 8) +CHAR("fi", 2, "fi", 2, "fi", 8) +CHAR("fl", 2, "fl", 2, "fl", 8) +CHAR("Fi", 2, "ffi", 3, "ffi", 8) +CHAR("Fl", 2, "ffl", 3, "ffl", 8) +CHAR("AE", 2, "AE", 2, "Æ", 6) +CHAR("ae", 2, "ae", 2, "æ", 6) +CHAR("OE", 2, "OE", 2, "Œ", 6) +CHAR("oe", 2, "oe", 2, "œ", 6) +CHAR("ss", 2, "ss", 2, "ß", 6) +CHAR("IJ", 2, "IJ", 2, "IJ", 6) +CHAR("ij", 2, "ij", 2, "ij", 6) + +/* Special letters. */ +CHAR("-D", 2, "D", 1, "Ð", 6) +CHAR("Sd", 2, "o", 1, "ð", 6) +CHAR("TP", 2, "b", 1, "Þ", 6) +CHAR("Tp", 2, "b", 1, "þ", 6) +CHAR(".i", 2, "i", 1, "ı", 6) +CHAR(".j", 2, "j", 1, "ȷ", 6) + +/* Currency. */ +CHAR("Do", 2, "$", 1, "$", 1) +CHAR("ct", 2, "c", 1, "¢", 6) +CHAR("Eu", 2, "EUR", 3, "€", 7) +CHAR("eu", 2, "EUR", 3, "€", 7) +CHAR("Ye", 2, "Y", 1, "¥", 6) +CHAR("Po", 2, "L", 1, "£", 6) +CHAR("Cs", 2, "x", 1, "¤", 6) +CHAR("Fn", 2, "f", 1, "ƒ", 6) + +/* Old style. */ +STRING("Am", 2, "&", 1, "&", 5) +STRING("Ba", 2, "|", 1, "|", 1) +STRING("Ge", 2, ">=", 2, "≥", 7) +STRING("Gt", 2, ">", 1, ">", 4) +STRING("If", 2, "infinity", 8, "infinity", 8) +STRING("Le", 2, "<=", 2, "≤", 7) +STRING("Lq", 2, "``", 2, "“", 7) +STRING("Lt", 2, "<", 1, "<", 4) +STRING("Na", 2, "NaN", 3, "NaN", 3) +STRING("Ne", 2, "!=", 2, "≠", 7) +STRING("Pi", 2, "pi", 2, "π", 6) +STRING("Pm", 2, "+-", 2, "±", 6) +STRING("R", 1, "(R)", 3, "®", 6) +STRING("Rq", 2, "\'\'", 2, "”", 7) +STRING("Tm", 2, "tm", 2, "™", 7) +STRING("left-bracket", 12, "[", 1, "[", 1) +STRING("left-parenthesis", 16, "(", 1, "(", 1) +STRING("left-singlequote", 16, "`", 1, "‘", 7) +STRING("lp", 2, "(", 1, "(", 1) +STRING("q", 1, "\"", 1, """, 6) +STRING("quote-left", 10, "`", 1, "‘", 7) +STRING("quote-right", 11, "\'", 1, "’", 7) +STRING("right-bracket", 13, "]", 1, "]", 1) +STRING("right-parenthesis", 17, ")", 1, ")", 1) +STRING("right-singlequote", 17, "\'", 1, "’", 7) +STRING("rp", 2, ")", 1, ")", 1) + +/* Lines. */ +CHAR("ba", 2, "|", 1, "|", 6) +CHAR("br", 2, "|", 1, "│", 7) +CHAR("ul", 2, "_", 1, "_", 5) +CHAR("rl", 2, "-", 1, "‾", 7) +CHAR("bb", 2, "|", 1, "¦", 6) +CHAR("sl", 2, "/", 1, "/", 5) +CHAR("rs", 2, "\\", 1, "\", 5) + +/* Text markers. */ +CHAR("ci", 2, "o", 1, "○", 7) +CHAR("bu", 2, "o", 1, "•", 7) +CHAR("dd", 2, "=", 1, "‡", 7) +CHAR("dg", 2, "-", 1, "†", 7) +CHAR("lz", 2, "<>", 2, "◊", 7) +CHAR("sq", 2, "[]", 2, "□", 7) +CHAR("ps", 2, "9|", 2, "¶", 6) +CHAR("sc", 2, "S", 1, "§", 6) +CHAR("lh", 2, "<=", 2, "☜", 7) +CHAR("rh", 2, "=>", 2, "☞", 7) +CHAR("at", 2, "@", 1, "@", 5) +CHAR("sh", 2, "#", 1, "#", 5) +CHAR("CR", 2, "_|", 2, "↵", 7) +CHAR("OK", 2, "\\/", 2, "✓", 8) + +/* Legal symbols. */ +CHAR("co", 2, "(C)", 3, "©", 6) +CHAR("rg", 2, "(R)", 3, "®", 6) +CHAR("tm", 2, "tm", 2, "™", 7) + +/* Punctuation. */ +CHAR(".", 1, ".", 1, ".", 1) +CHAR("r!", 2, "i", 1, "¡", 6) +CHAR("r?", 2, "c", 1, "¿", 6) +CHAR("em", 2, "--", 2, "—", 7) +CHAR("en", 2, "-", 1, "–", 7) +CHAR("hy", 2, "-", 1, "‐", 7) +CHAR("\\", 1, "\\", 1, "\\", 1) +CHAR("e", 1, "\\", 1, "\\", 1) + +/* Units. */ +CHAR("de", 2, "o", 1, "°", 6) +CHAR("%0", 2, "%o", 2, "‰", 7) +CHAR("fm", 2, "\'", 1, "′", 7) +CHAR("sd", 2, "\"", 1, "″", 7) +CHAR("mc", 2, "mu", 2, "µ", 6) diff --git a/usr.bin/mandoc/html.c b/usr.bin/mandoc/html.c new file mode 100644 index 0000000000..6703cd0605 --- /dev/null +++ b/usr.bin/mandoc/html.c @@ -0,0 +1,648 @@ +/* $Id: html.c,v 1.1 2009/10/21 19:13:50 schwarze Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "out.h" +#include "chars.h" +#include "html.h" +#include "main.h" + +#define UNCONST(a) ((void *)(uintptr_t)(const void *)(a)) + +#define DOCTYPE "-//W3C//DTD HTML 4.01//EN" +#define DTD "http://www.w3.org/TR/html4/strict.dtd" + +struct htmldata { + const char *name; + int flags; +#define HTML_CLRLINE (1 << 0) +#define HTML_NOSTACK (1 << 1) +}; + +static const struct htmldata htmltags[TAG_MAX] = { + {"html", HTML_CLRLINE}, /* TAG_HTML */ + {"head", HTML_CLRLINE}, /* TAG_HEAD */ + {"body", HTML_CLRLINE}, /* TAG_BODY */ + {"meta", HTML_CLRLINE | HTML_NOSTACK}, /* TAG_META */ + {"title", HTML_CLRLINE}, /* TAG_TITLE */ + {"div", HTML_CLRLINE}, /* TAG_DIV */ + {"h1", 0}, /* TAG_H1 */ + {"h2", 0}, /* TAG_H2 */ + {"p", HTML_CLRLINE}, /* TAG_P */ + {"span", 0}, /* TAG_SPAN */ + {"link", HTML_CLRLINE | HTML_NOSTACK}, /* TAG_LINK */ + {"br", HTML_CLRLINE | HTML_NOSTACK}, /* TAG_LINK */ + {"a", 0}, /* TAG_A */ + {"table", HTML_CLRLINE}, /* TAG_TABLE */ + {"col", HTML_CLRLINE | HTML_NOSTACK}, /* TAG_COL */ + {"tr", HTML_CLRLINE}, /* TAG_TR */ + {"td", HTML_CLRLINE}, /* TAG_TD */ + {"li", HTML_CLRLINE}, /* TAG_LI */ + {"ul", HTML_CLRLINE}, /* TAG_UL */ + {"ol", HTML_CLRLINE}, /* TAG_OL */ + {"base", HTML_CLRLINE | HTML_NOSTACK}, /* TAG_BASE */ +}; + +static const char *const htmlattrs[ATTR_MAX] = { + "http-equiv", + "content", + "name", + "rel", + "href", + "type", + "media", + "class", + "style", + "width", + "valign", + "target", + "id", +}; + +void * +html_alloc(char *outopts) +{ + struct html *h; + const char *toks[4]; + char *v; + + toks[0] = "style"; + toks[1] = "man"; + toks[2] = "includes"; + toks[3] = NULL; + + if (NULL == (h = calloc(1, sizeof(struct html)))) + return(NULL); + + SLIST_INIT(&h->tags); + SLIST_INIT(&h->ords); + + if (NULL == (h->symtab = chars_init(CHARS_HTML))) { + free(h); + return(NULL); + } + + while (outopts && *outopts) + switch (getsubopt(&outopts, UNCONST(toks), &v)) { + case (0): + h->style = v; + break; + case (1): + h->base_man = v; + break; + case (2): + h->base_includes = v; + break; + default: + break; + } + + return(h); +} + + +void +html_free(void *p) +{ + struct tag *tag; + struct ord *ord; + struct html *h; + + h = (struct html *)p; + + while ( ! SLIST_EMPTY(&h->ords)) { + ord = SLIST_FIRST(&h->ords); + SLIST_REMOVE_HEAD(&h->ords, entry); + free(ord); + } + + while ( ! SLIST_EMPTY(&h->tags)) { + tag = SLIST_FIRST(&h->tags); + SLIST_REMOVE_HEAD(&h->tags, entry); + free(tag); + } + + if (h->symtab) + chars_free(h->symtab); + + free(h); +} + + +void +print_gen_head(struct html *h) +{ + struct htmlpair tag[4]; + + tag[0].key = ATTR_HTTPEQUIV; + tag[0].val = "Content-Type"; + tag[1].key = ATTR_CONTENT; + tag[1].val = "text/html; charset=utf-8"; + print_otag(h, TAG_META, 2, tag); + + tag[0].key = ATTR_NAME; + tag[0].val = "resource-type"; + tag[1].key = ATTR_CONTENT; + tag[1].val = "document"; + print_otag(h, TAG_META, 2, tag); + + if (h->style) { + tag[0].key = ATTR_REL; + tag[0].val = "stylesheet"; + tag[1].key = ATTR_HREF; + tag[1].val = h->style; + tag[2].key = ATTR_TYPE; + tag[2].val = "text/css"; + tag[3].key = ATTR_MEDIA; + tag[3].val = "all"; + print_otag(h, TAG_LINK, 4, tag); + } +} + + +static void +print_spec(struct html *h, const char *p, int len) +{ + const char *rhs; + int i; + size_t sz; + + rhs = chars_a2ascii(h->symtab, p, (size_t)len, &sz); + + if (NULL == rhs) + return; + for (i = 0; i < (int)sz; i++) + putchar(rhs[i]); +} + + +static void +print_res(struct html *h, const char *p, int len) +{ + const char *rhs; + int i; + size_t sz; + + rhs = chars_a2res(h->symtab, p, (size_t)len, &sz); + + if (NULL == rhs) + return; + for (i = 0; i < (int)sz; i++) + putchar(rhs[i]); +} + + +static void +print_escape(struct html *h, const char **p) +{ + int j, type; + const char *wp; + + wp = *p; + type = 1; + + if (0 == *(++wp)) { + *p = wp; + return; + } + + if ('(' == *wp) { + wp++; + if (0 == *wp || 0 == *(wp + 1)) { + *p = 0 == *wp ? wp : wp + 1; + return; + } + + print_spec(h, wp, 2); + *p = ++wp; + return; + + } else if ('*' == *wp) { + if (0 == *(++wp)) { + *p = wp; + return; + } + + switch (*wp) { + case ('('): + wp++; + if (0 == *wp || 0 == *(wp + 1)) { + *p = 0 == *wp ? wp : wp + 1; + return; + } + + print_res(h, wp, 2); + *p = ++wp; + return; + case ('['): + type = 0; + break; + default: + print_res(h, wp, 1); + *p = wp; + return; + } + + } else if ('f' == *wp) { + if (0 == *(++wp)) { + *p = wp; + return; + } + + switch (*wp) { + case ('B'): + /* TODO */ + break; + case ('I'): + /* TODO */ + break; + case ('P'): + /* FALLTHROUGH */ + case ('R'): + /* TODO */ + break; + default: + break; + } + + *p = wp; + return; + + } else if ('[' != *wp) { + print_spec(h, wp, 1); + *p = wp; + return; + } + + wp++; + for (j = 0; *wp && ']' != *wp; wp++, j++) + /* Loop... */ ; + + if (0 == *wp) { + *p = wp; + return; + } + + if (type) + print_spec(h, wp - j, j); + else + print_res(h, wp - j, j); + + *p = wp; +} + + +static void +print_encode(struct html *h, const char *p) +{ + + for (; *p; p++) { + if ('\\' == *p) { + print_escape(h, &p); + continue; + } + switch (*p) { + case ('<'): + printf("<"); + break; + case ('>'): + printf(">"); + break; + case ('&'): + printf("&"); + break; + default: + putchar(*p); + break; + } + } +} + + +struct tag * +print_otag(struct html *h, enum htmltag tag, + int sz, const struct htmlpair *p) +{ + int i; + struct tag *t; + + if ( ! (HTML_NOSTACK & htmltags[tag].flags)) { + if (NULL == (t = malloc(sizeof(struct tag)))) + err(EXIT_FAILURE, "malloc"); + t->tag = tag; + SLIST_INSERT_HEAD(&h->tags, t, entry); + } else + t = NULL; + + if ( ! (HTML_NOSPACE & h->flags)) + if ( ! (HTML_CLRLINE & htmltags[tag].flags)) + printf(" "); + + printf("<%s", htmltags[tag].name); + for (i = 0; i < sz; i++) { + printf(" %s=\"", htmlattrs[p[i].key]); + assert(p->val); + print_encode(h, p[i].val); + printf("\""); + } + printf(">"); + + h->flags |= HTML_NOSPACE; + if (HTML_CLRLINE & htmltags[tag].flags) + h->flags |= HTML_NEWLINE; + else + h->flags &= ~HTML_NEWLINE; + + return(t); +} + + +/* ARGSUSED */ +static void +print_ctag(struct html *h, enum htmltag tag) +{ + + printf("", htmltags[tag].name); + if (HTML_CLRLINE & htmltags[tag].flags) + h->flags |= HTML_NOSPACE; + if (HTML_CLRLINE & htmltags[tag].flags) + h->flags |= HTML_NEWLINE; + else + h->flags &= ~HTML_NEWLINE; +} + + +/* ARGSUSED */ +void +print_gen_doctype(struct html *h) +{ + + printf("", DOCTYPE, DTD); +} + + +void +print_text(struct html *h, const char *p) +{ + + if (*p && 0 == *(p + 1)) + switch (*p) { + case('.'): + /* FALLTHROUGH */ + case(','): + /* FALLTHROUGH */ + case(';'): + /* FALLTHROUGH */ + case(':'): + /* FALLTHROUGH */ + case('?'): + /* FALLTHROUGH */ + case('!'): + /* FALLTHROUGH */ + case(')'): + /* FALLTHROUGH */ + case(']'): + /* FALLTHROUGH */ + case('}'): + if ( ! (HTML_IGNDELIM & h->flags)) + h->flags |= HTML_NOSPACE; + break; + default: + break; + } + + if ( ! (h->flags & HTML_NOSPACE)) + printf(" "); + + h->flags &= ~HTML_NOSPACE; + h->flags &= ~HTML_NEWLINE; + + if (p) + print_encode(h, p); + + if (*p && 0 == *(p + 1)) + switch (*p) { + case('('): + /* FALLTHROUGH */ + case('['): + /* FALLTHROUGH */ + case('{'): + h->flags |= HTML_NOSPACE; + break; + default: + break; + } +} + + +void +print_tagq(struct html *h, const struct tag *until) +{ + struct tag *tag; + + while ( ! SLIST_EMPTY(&h->tags)) { + tag = SLIST_FIRST(&h->tags); + print_ctag(h, tag->tag); + SLIST_REMOVE_HEAD(&h->tags, entry); + free(tag); + if (until && tag == until) + return; + } +} + + +void +print_stagq(struct html *h, const struct tag *suntil) +{ + struct tag *tag; + + while ( ! SLIST_EMPTY(&h->tags)) { + tag = SLIST_FIRST(&h->tags); + if (suntil && tag == suntil) + return; + print_ctag(h, tag->tag); + SLIST_REMOVE_HEAD(&h->tags, entry); + free(tag); + } +} + + +void +bufinit(struct html *h) +{ + + h->buf[0] = '\0'; + h->buflen = 0; +} + + +void +bufcat_style(struct html *h, const char *key, const char *val) +{ + + bufcat(h, key); + bufncat(h, ":", 1); + bufcat(h, val); + bufncat(h, ";", 1); +} + + +void +bufcat(struct html *h, const char *p) +{ + + bufncat(h, p, strlen(p)); +} + + +void +buffmt(struct html *h, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + (void)vsnprintf(h->buf + (int)h->buflen, + BUFSIZ - h->buflen - 1, fmt, ap); + va_end(ap); + h->buflen = strlen(h->buf); +} + + +void +bufncat(struct html *h, const char *p, size_t sz) +{ + + if (h->buflen + sz > BUFSIZ - 1) + sz = BUFSIZ - 1 - h->buflen; + + (void)strncat(h->buf, p, sz); + h->buflen += sz; +} + + +void +buffmt_includes(struct html *h, const char *name) +{ + const char *p, *pp; + + pp = h->base_includes; + + while (NULL != (p = strchr(pp, '%'))) { + bufncat(h, pp, (size_t)(p - pp)); + switch (*(p + 1)) { + case('I'): + bufcat(h, name); + break; + default: + bufncat(h, p, 2); + break; + } + pp = p + 2; + } + if (pp) + bufcat(h, pp); +} + + +void +buffmt_man(struct html *h, + const char *name, const char *sec) +{ + const char *p, *pp; + + pp = h->base_man; + + /* LINTED */ + while (NULL != (p = strchr(pp, '%'))) { + bufncat(h, pp, (size_t)(p - pp)); + switch (*(p + 1)) { + case('S'): + bufcat(h, sec ? sec : "1"); + break; + case('N'): + buffmt(h, name); + break; + default: + bufncat(h, p, 2); + break; + } + pp = p + 2; + } + if (pp) + bufcat(h, pp); +} + + +void +bufcat_su(struct html *h, const char *p, const struct roffsu *su) +{ + double v; + const char *u; + + v = su->scale; + + switch (su->unit) { + case (SCALE_CM): + u = "cm"; + break; + case (SCALE_IN): + u = "in"; + break; + case (SCALE_PC): + u = "pc"; + break; + case (SCALE_PT): + u = "pt"; + break; + case (SCALE_EM): + u = "em"; + break; + case (SCALE_MM): + if (0 == (v /= 100)) + v = 1; + u = "em"; + break; + case (SCALE_EN): + u = "ex"; + break; + case (SCALE_BU): + u = "ex"; + break; + case (SCALE_VS): + u = "em"; + break; + default: + u = "ex"; + break; + } + + if (su->pt) + buffmt(h, "%s: %f%s;", p, v, u); + else + /* LINTED */ + buffmt(h, "%s: %d%s;", p, (int)v, u); +} diff --git a/usr.bin/mandoc/html.h b/usr.bin/mandoc/html.h new file mode 100644 index 0000000000..758b0be0f5 --- /dev/null +++ b/usr.bin/mandoc/html.h @@ -0,0 +1,133 @@ +/* $Id: html.h,v 1.1 2009/10/21 19:13:50 schwarze Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef HTML_H +#define HTML_H + +__BEGIN_DECLS + +enum htmltag { + TAG_HTML, + TAG_HEAD, + TAG_BODY, + TAG_META, + TAG_TITLE, + TAG_DIV, + TAG_H1, + TAG_H2, + TAG_P, + TAG_SPAN, + TAG_LINK, + TAG_BR, + TAG_A, + TAG_TABLE, + TAG_COL, + TAG_TR, + TAG_TD, + TAG_LI, + TAG_UL, + TAG_OL, + TAG_BASE, + TAG_MAX +}; + +enum htmlattr { + ATTR_HTTPEQUIV, + ATTR_CONTENT, + ATTR_NAME, + ATTR_REL, + ATTR_HREF, + ATTR_TYPE, + ATTR_MEDIA, + ATTR_CLASS, + ATTR_STYLE, + ATTR_WIDTH, + ATTR_VALIGN, + ATTR_TARGET, + ATTR_ID, + ATTR_MAX +}; + +struct tag { + enum htmltag tag; + SLIST_ENTRY(tag) entry; +}; + +struct ord { + int pos; + const void *cookie; + SLIST_ENTRY(ord) entry; +}; + +SLIST_HEAD(tagq, tag); +SLIST_HEAD(ordq, ord); + +struct htmlpair { + enum htmlattr key; + const char *val; +}; + +#define PAIR_CLASS_INIT(p, v) \ + do { (p)->key = ATTR_CLASS; \ + (p)->val = (v); } while (/* CONSTCOND */ 0) +#define PAIR_HREF_INIT(p, v) \ + do { (p)->key = ATTR_HREF; \ + (p)->val = (v); } while (/* CONSTCOND */ 0) +#define PAIR_STYLE_INIT(p, h) \ + do { (p)->key = ATTR_STYLE; \ + (p)->val = (h)->buf; } while (/* CONSTCOND */ 0) + +struct html { + int flags; +#define HTML_NOSPACE (1 << 0) +#define HTML_NEWLINE (1 << 1) +#define HTML_IGNDELIM (1 << 2) + struct tagq tags; + struct ordq ords; + void *symtab; + char *base; + char *base_man; + char *base_includes; + char *style; + char buf[BUFSIZ]; + size_t buflen; +}; + +struct roffsu; + +void print_gen_doctype(struct html *); +void print_gen_head(struct html *); +struct tag *print_otag(struct html *, enum htmltag, + int, const struct htmlpair *); +void print_tagq(struct html *, const struct tag *); +void print_stagq(struct html *, const struct tag *); +void print_text(struct html *, const char *); + +void bufcat_su(struct html *, const char *, + const struct roffsu *); +void buffmt_man(struct html *, + const char *, const char *); +void buffmt_includes(struct html *, const char *); +void buffmt(struct html *, const char *, ...); +void bufcat(struct html *, const char *); +void bufcat_style(struct html *, + const char *, const char *); +void bufncat(struct html *, const char *, size_t); +void bufinit(struct html *); + +__END_DECLS + +#endif /*!HTML_H*/ diff --git a/usr.bin/mandoc/lib.c b/usr.bin/mandoc/lib.c new file mode 100644 index 0000000000..3cd2ab98bc --- /dev/null +++ b/usr.bin/mandoc/lib.c @@ -0,0 +1,32 @@ +/* $Id: lib.c,v 1.2 2009/06/14 23:00:57 schwarze Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include + +#include "libmdoc.h" + +#define LINE(x, y) \ + if (0 == strcmp(p, x)) return(y); + +const char * +mdoc_a2lib(const char *p) +{ + +#include "lib.in" + + return(NULL); +} diff --git a/usr.bin/mandoc/lib.in b/usr.bin/mandoc/lib.in new file mode 100644 index 0000000000..68a8c8b5a9 --- /dev/null +++ b/usr.bin/mandoc/lib.in @@ -0,0 +1,57 @@ +/* $Id: lib.in,v 1.2 2009/06/14 23:00:57 schwarze Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * These are all possible .Lb strings. When a new library is added, add + * its short-string to the left-hand side and formatted string to the + * right-hand side. + * + * Be sure to escape strings. + */ + +LINE("libarm", "ARM Architecture Library (libarm, \\-larm)") +LINE("libarm32", "ARM32 Architecture Library (libarm32, \\-larm32)") +LINE("libc", "Standard C Library (libc, \\-lc)") +LINE("libcdk", "Curses Development Kit Library (libcdk, \\-lcdk)") +LINE("libcompat", "Compatibility Library (libcompat, \\-lcompat)") +LINE("libcrypt", "Crypt Library (libcrypt, \\-lcrypt)") +LINE("libcurses", "Curses Library (libcurses, \\-lcurses)") +LINE("libedit", "Command Line Editor Library (libedit, \\-ledit)") +LINE("libevent", "Event Notification Library (libevent, \\-levent)") +LINE("libform", "Curses Form Library (libform, \\-lform)") +LINE("libi386", "i386 Architecture Library (libi386, \\-li386)") +LINE("libintl", "Internationalized Message Handling Library (libintl, \\-lintl)") +LINE("libipsec", "IPsec Policy Control Library (libipsec, \\-lipsec)") +LINE("libkvm", "Kernel Data Access Library (libkvm, \\-lkvm)") +LINE("libm", "Math Library (libm, \\-lm)") +LINE("libm68k", "m68k Architecture Library (libm68k, \\-lm68k)") +LINE("libmagic", "Magic Number Recognition Library (libmagic, \\-lmagic)") +LINE("libmenu", "Curses Menu Library (libmenu, \\-lmenu)") +LINE("libossaudio", "OSS Audio Emulation Library (libossaudio, \\-lossaudio)") +LINE("libpam", "Pluggable Authentication Module Library (libpam, \\-lpam)") +LINE("libpcap", "Capture Library (libpcap, \\-lpcap)") +LINE("libpci", "PCI Bus Access Library (libpci, \\-lpci)") +LINE("libpmc", "Performance Counters Library (libpmc, \\-lpmc)") +LINE("libposix", "POSIX Compatibility Library (libposix, \\-lposix)") +LINE("libpthread", "POSIX Threads Library (libpthread, \\-lpthread)") +LINE("libresolv", "DNS Resolver Library (libresolv, \\-lresolv)") +LINE("librt", "POSIX Real\\-time Library (librt, -lrt)") +LINE("libtermcap", "Termcap Access Library (libtermcap, \\-ltermcap)") +LINE("libusbhid", "USB Human Interface Devices Library (libusbhid, \\-lusbhid)") +LINE("libutil", "System Utilities Library (libutil, \\-lutil)") +LINE("libx86_64", "x86_64 Architecture Library (libx86_64, \\-lx86_64)") +LINE("libz", "Compression Library (libz, \\-lz)") diff --git a/usr.bin/mandoc/libman.h b/usr.bin/mandoc/libman.h new file mode 100644 index 0000000000..f0a5de14a4 --- /dev/null +++ b/usr.bin/mandoc/libman.h @@ -0,0 +1,116 @@ +/* $Id: libman.h,v 1.9 2009/09/21 21:11:36 schwarze Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef LIBMAN_H +#define LIBMAN_H + +#include "man.h" + +enum man_next { + MAN_NEXT_SIBLING = 0, + MAN_NEXT_CHILD +}; + +struct man { + void *data; + struct man_cb cb; + int pflags; + int flags; +#define MAN_HALT (1 << 0) +#define MAN_ELINE (1 << 1) /* Next-line element scope. */ +#define MAN_BLINE (1 << 2) /* Next-line block scope. */ +#define MAN_LITERAL (1 << 3) /* Literal input. */ + enum man_next next; + struct man_node *last; + struct man_node *first; + struct man_meta meta; +}; + +enum merr { + WNPRINT = 0, + WNMEM, + WMSEC, + WDATE, + WLNSCOPE, + WTSPACE, + WTQUOTE, + WNODATA, + WNOTITLE, + WESCAPE, + WNUMFMT, + WHEADARGS, + WBODYARGS, + WNHEADARGS, + WMACRO, + WMACROFORM, + WEXITSCOPE, + WNOSCOPE, + WOLITERAL, + WNLITERAL, + WERRMAX +}; + +#define MACRO_PROT_ARGS struct man *m, int tok, int line, \ + int ppos, int *pos, char *buf + +struct man_macro { + int (*fp)(MACRO_PROT_ARGS); + int flags; +#define MAN_SCOPED (1 << 0) +#define MAN_EXPLICIT (1 << 1) /* See blk_imp(). */ +#define MAN_FSCOPED (1 << 2) /* See blk_imp(). */ +}; + +extern const struct man_macro *const man_macros; + +__BEGIN_DECLS + +#define man_perr(m, l, p, t) \ + man_err((m), (l), (p), 1, (t)) +#define man_pwarn(m, l, p, t) \ + man_err((m), (l), (p), 0, (t)) +#define man_nerr(m, n, t) \ + man_err((m), (n)->line, (n)->pos, 1, (t)) +#define man_nwarn(m, n, t) \ + man_err((m), (n)->line, (n)->pos, 0, (t)) + +int man_word_alloc(struct man *, int, int, const char *); +int man_block_alloc(struct man *, int, int, int); +int man_head_alloc(struct man *, int, int, int); +int man_body_alloc(struct man *, int, int, int); +int man_elem_alloc(struct man *, int, int, int); +void man_node_free(struct man_node *); +void man_node_freelist(struct man_node *); +void man_hash_init(void); +int man_hash_find(const char *); +int man_macroend(struct man *); +int man_args(struct man *, int, int *, char *, char **); +#define ARGS_ERROR (-1) +#define ARGS_EOLN (0) +#define ARGS_WORD (1) +#define ARGS_QWORD (1) +int man_err(struct man *, int, int, int, enum merr); +int man_vwarn(struct man *, int, int, const char *, ...); +int man_verr(struct man *, int, int, const char *, ...); +int man_valid_post(struct man *); +int man_valid_pre(struct man *, const struct man_node *); +int man_action_post(struct man *); +int man_action_pre(struct man *, struct man_node *); +int man_unscope(struct man *, const struct man_node *); + +__END_DECLS + +#endif /*!LIBMAN_H*/ diff --git a/usr.bin/mandoc/libmandoc.h b/usr.bin/mandoc/libmandoc.h new file mode 100644 index 0000000000..8441ec35d0 --- /dev/null +++ b/usr.bin/mandoc/libmandoc.h @@ -0,0 +1,26 @@ +/* $Id: libmandoc.h,v 1.1 2009/07/08 00:04:10 schwarze Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef LIBMANDOC_H +#define LIBMANDOC_H + +__BEGIN_DECLS + +int mandoc_special(const char *); + +__END_DECLS + +#endif /*!LIBMANDOC_H*/ diff --git a/usr.bin/mandoc/libmdoc.h b/usr.bin/mandoc/libmdoc.h new file mode 100644 index 0000000000..90aded7bdc --- /dev/null +++ b/usr.bin/mandoc/libmdoc.h @@ -0,0 +1,188 @@ +/* $Id: libmdoc.h,v 1.23 2009/10/21 19:13:50 schwarze Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef LIBMDOC_H +#define LIBMDOC_H + +#include "mdoc.h" + +enum mdoc_next { + MDOC_NEXT_SIBLING = 0, + MDOC_NEXT_CHILD +}; + +struct mdoc { + void *data; + struct mdoc_cb cb; + int flags; +#define MDOC_HALT (1 << 0) /* Error in parse. Halt. */ +#define MDOC_LITERAL (1 << 1) /* In a literal scope. */ +#define MDOC_PBODY (1 << 2) /* In the document body. */ + int pflags; + enum mdoc_next next; + struct mdoc_node *last; + struct mdoc_node *first; + struct mdoc_meta meta; + enum mdoc_sec lastnamed; + enum mdoc_sec lastsec; +}; + +enum merr { + ETAILWS = 0, + EQUOTPARM, + EQUOTTERM, + EMALLOC, + EARGVAL, + EBODYPROL, + EPROLBODY, + ETEXTPROL, + ENOBLANK, + ETOOLONG, + EESCAPE, + EPRINT, + ENODAT, + ENOPROLOGUE, + ELINE, + EATT, + ENAME, + ELISTTYPE, + EDISPTYPE, + EMULTIDISP, + EMULTILIST, + ESECNAME, + ENAMESECINC, + EARGREP, + EBOOL, + ECOLMIS, + ENESTDISP, + EMISSWIDTH, + EWRONGMSEC, + ESECOOO, + ESECREP, + EBADSTAND, + ENOMULTILINE, + EMULTILINE, + ENOLINE, + EPROLOOO, + EPROLREP, + EBADMSEC, + EBADSEC, + EFONT, + EBADDATE, + ENUMFMT, + ENOWIDTH, + EUTSNAME, + EOBS, + EIMPBRK, + EIGNE, + EOPEN, + EQUOTPHR, + ENOCTX, + ELIB, + EBADCHILD, + ENOTYPE, + MERRMAX +}; + +#define MACRO_PROT_ARGS struct mdoc *m, int tok, int line, \ + int ppos, int *pos, char *buf + +struct mdoc_macro { + int (*fp)(MACRO_PROT_ARGS); + int flags; +#define MDOC_CALLABLE (1 << 0) +#define MDOC_PARSED (1 << 1) +#define MDOC_EXPLICIT (1 << 2) +#define MDOC_PROLOGUE (1 << 3) +#define MDOC_IGNDELIM (1 << 4) + /* Reserved words in arguments treated as text. */ +}; + +extern const struct mdoc_macro *const mdoc_macros; + +__BEGIN_DECLS + +#define mdoc_perr(m, l, p, t) \ + mdoc_err((m), (l), (p), 1, (t)) +#define mdoc_pwarn(m, l, p, t) \ + mdoc_err((m), (l), (p), 0, (t)) +#define mdoc_nerr(m, n, t) \ + mdoc_err((m), (n)->line, (n)->pos, 1, (t)) +#define mdoc_nwarn(m, n, t) \ + mdoc_err((m), (n)->line, (n)->pos, 0, (t)) + +int mdoc_err(struct mdoc *, int, int, int, enum merr); +int mdoc_verr(struct mdoc *, int, int, const char *, ...); +int mdoc_vwarn(struct mdoc *, int, int, const char *, ...); + +int mdoc_macro(MACRO_PROT_ARGS); +int mdoc_word_alloc(struct mdoc *, + int, int, const char *); +int mdoc_elem_alloc(struct mdoc *, int, int, + int, struct mdoc_arg *); +int mdoc_block_alloc(struct mdoc *, int, int, + int, struct mdoc_arg *); +int mdoc_head_alloc(struct mdoc *, int, int, int); +int mdoc_tail_alloc(struct mdoc *, int, int, int); +int mdoc_body_alloc(struct mdoc *, int, int, int); +void mdoc_node_free(struct mdoc_node *); +void mdoc_node_freelist(struct mdoc_node *); +void mdoc_hash_init(void); +int mdoc_hash_find(const char *); +int mdoc_iscdelim(char); +int mdoc_isdelim(const char *); +size_t mdoc_isescape(const char *); +enum mdoc_sec mdoc_atosec(const char *); +time_t mdoc_atotime(const char *); + +size_t mdoc_macro2len(int); +const char *mdoc_a2att(const char *); +const char *mdoc_a2lib(const char *); +const char *mdoc_a2st(const char *); +const char *mdoc_a2arch(const char *); +const char *mdoc_a2vol(const char *); +const char *mdoc_a2msec(const char *); +int mdoc_valid_pre(struct mdoc *, + const struct mdoc_node *); +int mdoc_valid_post(struct mdoc *); +int mdoc_action_pre(struct mdoc *, + const struct mdoc_node *); +int mdoc_action_post(struct mdoc *); +int mdoc_argv(struct mdoc *, int, int, + struct mdoc_arg **, int *, char *); +#define ARGV_ERROR (-1) +#define ARGV_EOLN (0) +#define ARGV_ARG (1) +#define ARGV_WORD (2) +void mdoc_argv_free(struct mdoc_arg *); +int mdoc_args(struct mdoc *, int, + int *, char *, int, char **); +int mdoc_zargs(struct mdoc *, int, + int *, char *, int, char **); +#define ARGS_DELIM (1 << 1) /* See args(). */ +#define ARGS_TABSEP (1 << 2) /* See args(). */ +#define ARGS_NOWARN (1 << 3) /* See args(). */ +#define ARGS_ERROR (-1) +#define ARGS_EOLN (0) +#define ARGS_WORD (1) +#define ARGS_PUNCT (2) +#define ARGS_QWORD (3) +#define ARGS_PHRASE (4) +int mdoc_macroend(struct mdoc *); + +__END_DECLS + +#endif /*!LIBMDOC_H*/ diff --git a/usr.bin/mandoc/main.c b/usr.bin/mandoc/main.c new file mode 100644 index 0000000000..03b8f2c693 --- /dev/null +++ b/usr.bin/mandoc/main.c @@ -0,0 +1,662 @@ +/* $Id: main.c,v 1.17 2009/10/21 19:13:50 schwarze Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mdoc.h" +#include "man.h" +#include "main.h" + +#define UNCONST(a) ((void *)(uintptr_t)(const void *)(a)) + +typedef void (*out_mdoc)(void *, const struct mdoc *); +typedef void (*out_man)(void *, const struct man *); +typedef void (*out_free)(void *); + +struct buf { + char *buf; + size_t sz; +}; + +enum intt { + INTT_AUTO, + INTT_MDOC, + INTT_MAN +}; + +enum outt { + OUTT_ASCII = 0, + OUTT_TREE, + OUTT_HTML, + OUTT_LINT +}; + +struct curparse { + const char *file; /* Current parse. */ + int fd; /* Current parse. */ + int wflags; +#define WARN_WALL (1 << 0) /* All-warnings mask. */ +#define WARN_WERR (1 << 2) /* Warnings->errors. */ + int fflags; +#define IGN_SCOPE (1 << 0) /* Ignore scope errors. */ +#define NO_IGN_ESCAPE (1 << 1) /* Don't ignore bad escapes. */ +#define NO_IGN_MACRO (1 << 2) /* Don't ignore bad macros. */ +#define NO_IGN_CHARS (1 << 3) /* Don't ignore bad chars. */ +#define IGN_ERRORS (1 << 4) /* Ignore failed parse. */ + enum intt inttype; /* Input parsers... */ + struct man *man; + struct man *lastman; + struct mdoc *mdoc; + struct mdoc *lastmdoc; + enum outt outtype; /* Output devices... */ + out_mdoc outmdoc; + out_man outman; + out_free outfree; + void *outdata; + char *outopts; +}; + +static int foptions(int *, char *); +static int toptions(enum outt *, char *); +static int moptions(enum intt *, char *); +static int woptions(int *, char *); +static int merr(void *, int, int, const char *); +static int mwarn(void *, int, int, const char *); +static int ffile(struct buf *, struct buf *, + const char *, struct curparse *); +static int fdesc(struct buf *, struct buf *, + struct curparse *); +static int pset(const char *, int, struct curparse *, + struct man **, struct mdoc **); +static struct man *man_init(struct curparse *); +static struct mdoc *mdoc_init(struct curparse *); +__dead2 static void version(void); +__dead2 static void usage(void); + +extern char *__progname; + + +int +main(int argc, char *argv[]) +{ + int c, rc; + struct buf ln, blk; + struct curparse curp; + + bzero(&curp, sizeof(struct curparse)); + + curp.inttype = INTT_AUTO; + curp.outtype = OUTT_ASCII; + + /* LINTED */ + while (-1 != (c = getopt(argc, argv, "f:m:o:T:VW:"))) + switch (c) { + case ('f'): + if ( ! foptions(&curp.fflags, optarg)) + return(EXIT_FAILURE); + break; + case ('m'): + if ( ! moptions(&curp.inttype, optarg)) + return(EXIT_FAILURE); + break; + case ('o'): + curp.outopts = optarg; + break; + case ('T'): + if ( ! toptions(&curp.outtype, optarg)) + return(EXIT_FAILURE); + break; + case ('W'): + if ( ! woptions(&curp.wflags, optarg)) + return(EXIT_FAILURE); + break; + case ('V'): + version(); + /* NOTREACHED */ + default: + usage(); + /* NOTREACHED */ + } + + argc -= optind; + argv += optind; + + bzero(&ln, sizeof(struct buf)); + bzero(&blk, sizeof(struct buf)); + + rc = 1; + + if (NULL == *argv) { + curp.file = ""; + curp.fd = STDIN_FILENO; + + c = fdesc(&blk, &ln, &curp); + if ( ! (IGN_ERRORS & curp.fflags)) + rc = 1 == c ? 1 : 0; + else + rc = -1 == c ? 0 : 1; + } + + while (rc && *argv) { + c = ffile(&blk, &ln, *argv, &curp); + if ( ! (IGN_ERRORS & curp.fflags)) + rc = 1 == c ? 1 : 0; + else + rc = -1 == c ? 0 : 1; + + argv++; + if (*argv && rc) { + if (curp.lastman) + if ( ! man_reset(curp.lastman)) + rc = 0; + if (curp.lastmdoc) + if ( ! mdoc_reset(curp.lastmdoc)) + rc = 0; + curp.lastman = NULL; + curp.lastmdoc = NULL; + } + } + + if (blk.buf) + free(blk.buf); + if (ln.buf) + free(ln.buf); + if (curp.outfree) + (*curp.outfree)(curp.outdata); + if (curp.mdoc) + mdoc_free(curp.mdoc); + if (curp.man) + man_free(curp.man); + + return(rc ? EXIT_SUCCESS : EXIT_FAILURE); +} + + +__dead2 static void +version(void) +{ + + (void)printf("%s %s\n", __progname, VERSION); + exit(EXIT_SUCCESS); +} + + +__dead2 static void +usage(void) +{ + + (void)fprintf(stderr, "usage: %s [-V] [-foption...] " + "[-mformat] [-Toutput] [-Werr...]\n", + __progname); + exit(EXIT_FAILURE); +} + + +static struct man * +man_init(struct curparse *curp) +{ + int pflags; + struct man *man; + struct man_cb mancb; + + mancb.man_err = merr; + mancb.man_warn = mwarn; + + /* Defaults from mandoc.1. */ + + pflags = MAN_IGN_MACRO | MAN_IGN_ESCAPE | MAN_IGN_CHARS; + + if (curp->fflags & NO_IGN_MACRO) + pflags &= ~MAN_IGN_MACRO; + if (curp->fflags & NO_IGN_CHARS) + pflags &= ~MAN_IGN_CHARS; + if (curp->fflags & NO_IGN_ESCAPE) + pflags &= ~MAN_IGN_ESCAPE; + + if (NULL == (man = man_alloc(curp, pflags, &mancb))) + warnx("memory exhausted"); + + return(man); +} + + +static struct mdoc * +mdoc_init(struct curparse *curp) +{ + int pflags; + struct mdoc *mdoc; + struct mdoc_cb mdoccb; + + mdoccb.mdoc_err = merr; + mdoccb.mdoc_warn = mwarn; + + /* Defaults from mandoc.1. */ + + pflags = MDOC_IGN_MACRO | MDOC_IGN_ESCAPE | MDOC_IGN_CHARS; + + if (curp->fflags & IGN_SCOPE) + pflags |= MDOC_IGN_SCOPE; + if (curp->fflags & NO_IGN_ESCAPE) + pflags &= ~MDOC_IGN_ESCAPE; + if (curp->fflags & NO_IGN_MACRO) + pflags &= ~MDOC_IGN_MACRO; + if (curp->fflags & NO_IGN_CHARS) + pflags &= ~MDOC_IGN_CHARS; + + if (NULL == (mdoc = mdoc_alloc(curp, pflags, &mdoccb))) + warnx("memory exhausted"); + + return(mdoc); +} + + +static int +ffile(struct buf *blk, struct buf *ln, + const char *file, struct curparse *curp) +{ + int c; + + curp->file = file; + if (-1 == (curp->fd = open(curp->file, O_RDONLY, 0))) { + warn("%s", curp->file); + return(-1); + } + + c = fdesc(blk, ln, curp); + + if (-1 == close(curp->fd)) + warn("%s", curp->file); + + return(c); +} + + +static int +fdesc(struct buf *blk, struct buf *ln, struct curparse *curp) +{ + size_t sz; + ssize_t ssz; + struct stat st; + int j, i, pos, lnn, comment; + struct man *man; + struct mdoc *mdoc; + + sz = BUFSIZ; + man = NULL; + mdoc = NULL; + + /* + * Two buffers: ln and buf. buf is the input buffer optimised + * here for each file's block size. ln is a line buffer. Both + * growable, hence passed in by ptr-ptr. + */ + + if (-1 == fstat(curp->fd, &st)) + warn("%s", curp->file); + else if ((size_t)st.st_blksize > sz) + sz = st.st_blksize; + + if (sz > blk->sz) { + blk->buf = realloc(blk->buf, sz); + if (NULL == blk->buf) { + warn("realloc"); + return(-1); + } + blk->sz = sz; + } + + /* Fill buf with file blocksize. */ + + for (lnn = pos = comment = 0; ; ) { + if (-1 == (ssz = read(curp->fd, blk->buf, sz))) { + warn("%s", curp->file); + return(-1); + } else if (0 == ssz) + break; + + /* Parse the read block into partial or full lines. */ + + for (i = 0; i < (int)ssz; i++) { + if (pos >= (int)ln->sz) { + ln->sz += 256; /* Step-size. */ + ln->buf = realloc(ln->buf, ln->sz); + if (NULL == ln->buf) { + warn("realloc"); + return(-1); + } + } + + if ('\n' != blk->buf[i]) { + if (comment) + continue; + ln->buf[pos++] = blk->buf[i]; + + /* Handle in-line `\"' comments. */ + + if (1 == pos || '\"' != ln->buf[pos - 1]) + continue; + + for (j = pos - 2; j >= 0; j--) + if ('\\' != ln->buf[j]) + break; + + if ( ! ((pos - 2 - j) % 2)) + continue; + + comment = 1; + pos -= 2; + continue; + } + + /* Handle escaped `\\n' newlines. */ + + if (pos > 0 && 0 == comment && + '\\' == ln->buf[pos - 1]) { + for (j = pos - 1; j >= 0; j--) + if ('\\' != ln->buf[j]) + break; + if ( ! ((pos - j) % 2)) { + pos--; + lnn++; + continue; + } + } + + ln->buf[pos] = 0; + lnn++; + + /* If unset, assign parser in pset(). */ + + if ( ! (man || mdoc) && ! pset(ln->buf, + pos, curp, &man, &mdoc)) + return(-1); + + pos = comment = 0; + + /* Pass down into parsers. */ + + if (man && ! man_parseln(man, lnn, ln->buf)) + return(0); + if (mdoc && ! mdoc_parseln(mdoc, lnn, ln->buf)) + return(0); + } + } + + /* NOTE a parser may not have been assigned, yet. */ + + if ( ! (man || mdoc)) { + (void)fprintf(stderr, "%s: not a manual\n", + curp->file); + return(0); + } + + if (mdoc && ! mdoc_endparse(mdoc)) + return(0); + if (man && ! man_endparse(man)) + return(0); + + /* If unset, allocate output dev now (if applicable). */ + + if ( ! (curp->outman && curp->outmdoc)) { + switch (curp->outtype) { + case (OUTT_HTML): + curp->outdata = html_alloc(curp->outopts); + curp->outman = html_man; + curp->outmdoc = html_mdoc; + curp->outfree = html_free; + break; + case (OUTT_TREE): + curp->outman = tree_man; + curp->outmdoc = tree_mdoc; + break; + case (OUTT_LINT): + break; + default: + curp->outdata = ascii_alloc(); + curp->outman = terminal_man; + curp->outmdoc = terminal_mdoc; + curp->outfree = terminal_free; + break; + } + } + + /* Execute the out device, if it exists. */ + + if (man && curp->outman) + (*curp->outman)(curp->outdata, man); + if (mdoc && curp->outmdoc) + (*curp->outmdoc)(curp->outdata, mdoc); + + return(1); +} + + +static int +pset(const char *buf, int pos, struct curparse *curp, + struct man **man, struct mdoc **mdoc) +{ + int i; + + /* + * Try to intuit which kind of manual parser should be used. If + * passed in by command-line (-man, -mdoc), then use that + * explicitly. If passed as -mandoc, then try to guess from the + * line: either skip dot-lines, use -mdoc when finding `.Dt', or + * default to -man, which is more lenient. + */ + + if (buf[0] == '.') { + for (i = 1; buf[i]; i++) + if (' ' != buf[i] && '\t' != buf[i]) + break; + if (0 == buf[i]) + return(1); + } + + switch (curp->inttype) { + case (INTT_MDOC): + if (NULL == curp->mdoc) + curp->mdoc = mdoc_init(curp); + if (NULL == (*mdoc = curp->mdoc)) + return(0); + curp->lastmdoc = *mdoc; + return(1); + case (INTT_MAN): + if (NULL == curp->man) + curp->man = man_init(curp); + if (NULL == (*man = curp->man)) + return(0); + curp->lastman = *man; + return(1); + default: + break; + } + + if (pos >= 3 && 0 == memcmp(buf, ".Dd", 3)) { + if (NULL == curp->mdoc) + curp->mdoc = mdoc_init(curp); + if (NULL == (*mdoc = curp->mdoc)) + return(0); + curp->lastmdoc = *mdoc; + return(1); + } + + if (NULL == curp->man) + curp->man = man_init(curp); + if (NULL == (*man = curp->man)) + return(0); + curp->lastman = *man; + return(1); +} + + +static int +moptions(enum intt *tflags, char *arg) +{ + + if (0 == strcmp(arg, "doc")) + *tflags = INTT_MDOC; + else if (0 == strcmp(arg, "andoc")) + *tflags = INTT_AUTO; + else if (0 == strcmp(arg, "an")) + *tflags = INTT_MAN; + else { + warnx("bad argument: -m%s", arg); + return(0); + } + + return(1); +} + + +static int +toptions(enum outt *tflags, char *arg) +{ + + if (0 == strcmp(arg, "ascii")) + *tflags = OUTT_ASCII; + else if (0 == strcmp(arg, "lint")) + *tflags = OUTT_LINT; + else if (0 == strcmp(arg, "tree")) + *tflags = OUTT_TREE; + else if (0 == strcmp(arg, "html")) + *tflags = OUTT_HTML; + else { + warnx("bad argument: -T%s", arg); + return(0); + } + + return(1); +} + + +static int +foptions(int *fflags, char *arg) +{ + char *v, *o; + const char *toks[7]; + + toks[0] = "ign-scope"; + toks[1] = "no-ign-escape"; + toks[2] = "no-ign-macro"; + toks[3] = "no-ign-chars"; + toks[4] = "ign-errors"; + toks[5] = "strict"; + toks[6] = NULL; + + while (*arg) { + o = arg; + switch (getsubopt(&arg, UNCONST(toks), &v)) { + case (0): + *fflags |= IGN_SCOPE; + break; + case (1): + *fflags |= NO_IGN_ESCAPE; + break; + case (2): + *fflags |= NO_IGN_MACRO; + break; + case (3): + *fflags |= NO_IGN_CHARS; + break; + case (4): + *fflags |= IGN_ERRORS; + break; + case (5): + *fflags |= NO_IGN_ESCAPE | + NO_IGN_MACRO | NO_IGN_CHARS; + break; + default: + warnx("bad argument: -f%s", o); + return(0); + } + } + + return(1); +} + + +static int +woptions(int *wflags, char *arg) +{ + char *v, *o; + const char *toks[3]; + + toks[0] = "all"; + toks[1] = "error"; + toks[2] = NULL; + + while (*arg) { + o = arg; + switch (getsubopt(&arg, UNCONST(toks), &v)) { + case (0): + *wflags |= WARN_WALL; + break; + case (1): + *wflags |= WARN_WERR; + break; + default: + warnx("bad argument: -W%s", o); + return(0); + } + } + + return(1); +} + + +/* ARGSUSED */ +static int +merr(void *arg, int line, int col, const char *msg) +{ + struct curparse *curp; + + curp = (struct curparse *)arg; + + (void)fprintf(stderr, "%s:%d:%d: error: %s\n", + curp->file, line, col + 1, msg); + + return(0); +} + + +static int +mwarn(void *arg, int line, int col, const char *msg) +{ + struct curparse *curp; + + curp = (struct curparse *)arg; + + if ( ! (curp->wflags & WARN_WALL)) + return(1); + + (void)fprintf(stderr, "%s:%d:%d: warning: %s\n", + curp->file, line, col + 1, msg); + + if ( ! (curp->wflags & WARN_WERR)) + return(1); + + return(0); +} diff --git a/usr.bin/mandoc/main.h b/usr.bin/mandoc/main.h new file mode 100644 index 0000000000..8eaa82c069 --- /dev/null +++ b/usr.bin/mandoc/main.h @@ -0,0 +1,47 @@ +/* $Id: main.h,v 1.1 2009/10/21 19:13:50 schwarze Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef MAIN_H +#define MAIN_H + +__BEGIN_DECLS + +struct mdoc; +struct man; + +/* + * Definitions for main.c-visible output device functions, e.g., -Thtml + * and -Tascii. Note that ascii_alloc() is named as such in + * anticipation of latin1_alloc() and so on, all of which map into the + * terminal output routines with different character settings. + */ + +void *html_alloc(char *); +void html_mdoc(void *, const struct mdoc *); +void html_man(void *, const struct man *); +void html_free(void *); + +void tree_mdoc(void *, const struct mdoc *); +void tree_man(void *, const struct man *); + +void *ascii_alloc(void); +void terminal_mdoc(void *, const struct mdoc *); +void terminal_man(void *, const struct man *); +void terminal_free(void *); + +__END_DECLS + +#endif /*!MAIN_H*/ diff --git a/usr.bin/mandoc/man.3 b/usr.bin/mandoc/man.3 new file mode 100644 index 0000000000..acb2874464 --- /dev/null +++ b/usr.bin/mandoc/man.3 @@ -0,0 +1,283 @@ +.\" $Id: man.3,v 1.6 2009/10/20 10:15:04 schwarze Exp $ +.\" +.\" Copyright (c) 2009 Kristaps Dzonsons +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: August 22 2009 $ +.Dt MAN 3 +.Os +.\" SECTION +.Sh NAME +.Nm man_alloc , +.Nm man_parseln , +.Nm man_endparse , +.Nm man_node , +.Nm man_meta , +.Nm man_free , +.Nm man_reset +.Nd man macro compiler library +.\" SECTION +.Sh SYNOPSIS +.In man.h +.Vt extern const char * const * man_macronames; +.Ft "struct man *" +.Fn man_alloc "void *data" "int pflags" "const struct man_cb *cb" +.Ft void +.Fn man_reset "struct man *man" +.Ft void +.Fn man_free "struct man *man" +.Ft int +.Fn man_parseln "struct man *man" "int line" "char *buf" +.Ft "const struct man_node *" +.Fn man_node "const struct man *man" +.Ft "const struct man_meta *" +.Fn man_meta "const struct man *man" +.Ft int +.Fn man_endparse "struct man *man" +.\" SECTION +.Sh DESCRIPTION +The +.Nm man +library parses lines of +.Xr man 7 +input (and +.Em only +man) into an abstract syntax tree (AST). +.\" PARAGRAPH +.Pp +In general, applications initiate a parsing sequence with +.Fn man_alloc , +parse each line in a document with +.Fn man_parseln , +close the parsing session with +.Fn man_endparse , +operate over the syntax tree returned by +.Fn man_node +and +.Fn man_meta , +then free all allocated memory with +.Fn man_free . +The +.Fn man_reset +function may be used in order to reset the parser for another input +sequence. See the +.Sx EXAMPLES +section for a full example. +.\" PARAGRAPH +.Pp +This section further defines the +.Sx Types , +.Sx Functions +and +.Sx Variables +available to programmers. Following that, the +.Sx Abstract Syntax Tree +section documents the output tree. +.\" SUBSECTION +.Ss Types +Both functions (see +.Sx Functions ) +and variables (see +.Sx Variables ) +may use the following types: +.Bl -ohang -offset "XXXX" +.\" LIST-ITEM +.It Vt struct man +An opaque type defined in +.Pa man.c . +Its values are only used privately within the library. +.\" LIST-ITEM +.It Vt struct man_cb +A set of message callbacks defined in +.Pa man.h . +.\" LIST-ITEM +.It Vt struct man_node +A parsed node. Defined in +.Pa man.h . +See +.Sx Abstract Syntax Tree +for details. +.El +.\" SUBSECTION +.Ss Functions +Function descriptions follow: +.Bl -ohang -offset "XXXX" +.\" LIST-ITEM +.It Fn man_alloc +Allocates a parsing structure. The +.Fa data +pointer is passed to callbacks in +.Fa cb , +which are documented further in the header file. +The +.Fa pflags +arguments are defined in +.Pa man.h . +Returns NULL on failure. If non-NULL, the pointer must be freed with +.Fn man_free . +.\" LIST-ITEM +.It Fn man_reset +Reset the parser for another parse routine. After its use, +.Fn man_parseln +behaves as if invoked for the first time. +.\" LIST-ITEM +.It Fn man_free +Free all resources of a parser. The pointer is no longer valid after +invocation. +.\" LIST-ITEM +.It Fn man_parseln +Parse a nil-terminated line of input. This line should not contain the +trailing newline. Returns 0 on failure, 1 on success. The input buffer +.Fa buf +is modified by this function. +.\" LIST-ITEM +.It Fn man_endparse +Signals that the parse is complete. Note that if +.Fn man_endparse +is called subsequent to +.Fn man_node , +the resulting tree is incomplete. Returns 0 on failure, 1 on success. +.\" LIST-ITEM +.It Fn man_node +Returns the first node of the parse. Note that if +.Fn man_parseln +or +.Fn man_endparse +return 0, the tree will be incomplete. +.It Fn man_meta +Returns the document's parsed meta-data. If this information has not +yet been supplied or +.Fn man_parseln +or +.Fn man_endparse +return 0, the data will be incomplete. +.El +.\" SUBSECTION +.Ss Variables +The following variables are also defined: +.Bl -ohang -offset "XXXX" +.\" LIST-ITEM +.It Va man_macronames +An array of string-ified token names. +.El +.\" SUBSECTION +.Ss Abstract Syntax Tree +The +.Nm +functions produce an abstract syntax tree (AST) describing input in a +regular form. It may be reviewed at any time with +.Fn man_nodes ; +however, if called before +.Fn man_endparse , +or after +.Fn man_endparse +or +.Fn man_parseln +fail, it may be incomplete. +.\" PARAGRAPH +.Pp +This AST is governed by the ontological +rules dictated in +.Xr man 7 +and derives its terminology accordingly. +.\" PARAGRAPH +.Pp +The AST is composed of +.Vt struct man_node +nodes with element, root and text types as declared +by the +.Va type +field. Each node also provides its parse point (the +.Va line , +.Va sec , +and +.Va pos +fields), its position in the tree (the +.Va parent , +.Va child , +.Va next +and +.Va prev +fields) and some type-specific data. +.\" PARAGRAPH +.Pp +The tree itself is arranged according to the following normal form, +where capitalised non-terminals represent nodes. +.Pp +.Bl -tag -width "ELEMENTXX" -compact -offset "XXXX" +.\" LIST-ITEM +.It ROOT +\(<- mnode+ +.It mnode +\(<- ELEMENT | TEXT | BLOCK +.It BLOCK +\(<- HEAD BODY +.It HEAD +\(<- mnode* +.It BODY +\(<- mnode* +.It ELEMENT +\(<- ELEMENT | TEXT* +.It TEXT +\(<- [[:alpha:]]* +.El +.\" PARAGRAPH +.Pp +The only elements capable of nesting other elements are those with +next-lint scope as documented in +.Xr man 7 . +.\" SECTION +.Sh EXAMPLES +The following example reads lines from stdin and parses them, operating +on the finished parse tree with +.Fn parsed . +Note that, if the last line of the file isn't newline-terminated, this +will truncate the file's last character (see +.Xr fgetln 3 ) . +Further, this example does not error-check nor free memory upon failure. +.Bd -literal -offset "XXXX" +struct man *man; +struct man_node *node; +char *buf; +size_t len; +int line; + +line = 1; +man = man_alloc(NULL, 0, NULL); + +while ((buf = fgetln(fp, &len))) { + buf[len - 1] = '\\0'; + if ( ! man_parseln(man, line, buf)) + errx(1, "man_parseln"); + line++; +} + +if ( ! man_endparse(man)) + errx(1, "man_endparse"); +if (NULL == (node = man_node(man))) + errx(1, "man_node"); + +parsed(man, node); +man_free(man); +.Ed +.\" SECTION +.Sh SEE ALSO +.Xr mandoc 1 , +.Xr man 7 +.\" SECTION +.Sh AUTHORS +The +.Nm +utility was written by +.An Kristaps Dzonsons Aq kristaps@kth.se . diff --git a/usr.bin/mandoc/man.7 b/usr.bin/mandoc/man.7 new file mode 100644 index 0000000000..974eda2ad4 --- /dev/null +++ b/usr.bin/mandoc/man.7 @@ -0,0 +1,589 @@ +.\" $Id: man.7,v 1.12 2009/10/21 19:13:50 schwarze Exp $ +.\" +.\" Copyright (c) 2009 Kristaps Dzonsons +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: September 18 2009 $ +.Dt MAN 7 +.Os +. +. +.Sh NAME +.Nm man +.Nd man language reference +. +. +.Sh DESCRIPTION +The +.Nm man +language was historically used to format +.Ux +manuals. This reference document describes its syntax, structure, and +usage. +. +.Pp +.Bf -emphasis +Do not use +.Nm +to write your manuals. +.Ef +Use the +.Xr mdoc 7 +language, instead. +. +.Pp +An +.Nm +document follows simple rules: lines beginning with the control +character +.Sq \&. +are parsed for macros. Other lines are interpreted within the scope of +prior macros: +.Bd -literal -offset indent +\&.SH Macro lines change control state. +Other lines are interpreted within the current state. +.Ed +. +. +.Sh INPUT ENCODING +.Nm +documents may contain only graphable 7-bit ASCII characters, the +space character, and the tabs character. All manuals must have +.Ux +line termination. +. +.Pp +Blank lines are acceptable; where found, the output will assert a +vertical space. +. +.Pp +The +.Sq \ec +escape is common in historical +.Nm +documents; if encountered at the end of a word, it ensures that the +subsequent word isn't off-set by whitespace. +. +. +.Ss Comments +Text following a +.Sq \e\*" , +whether in a macro or free-form text line, is ignored to the end of +line. A macro line with only a control character and comment escape, +.Sq \&.\e" , +is also ignored. Macro lines with only a control charater and +optionally whitespace are stripped from input. +. +. +.Ss Special Characters +Special characters may occur in both macro and free-form lines. +Sequences begin with the escape character +.Sq \e +followed by either an open-parenthesis +.Sq \&( +for two-character sequences; an open-bracket +.Sq \&[ +for n-character sequences (terminated at a close-bracket +.Sq \&] ) ; +or a single one-character sequence. See +.Xr mandoc_char 7 +for a complete list. Examples include +.Sq \e(em +.Pq em-dash +and +.Sq \ee +.Pq back-slash . +. +. +.Ss Text Decoration +Terms may be text-decorated using the +.Sq \ef +escape followed by an indicator: B (bold), I, (italic), or P and R +(Roman, or reset). +. +. +.Ss Whitespace +Unless specifically escaped, consecutive blocks of whitespace are pruned +from input. These are later re-added, if applicable, by a front-end +utility such as +.Xr mandoc 1 . +. +.Ss Scaling Widths +Many macros support scaled widths for their arguments, such as +stipulating a two-inch paragraph indentation with the following: +.Bd -literal -offset indent +\&.HP 2i +.Ed +. +.Pp +The syntax for scaled widths is +.Sq Li [+-]?[0-9]*.[0-9]*[:unit:]? , +where a decimal must be preceded or proceeded by at least one digit. +Negative numbers, while accepted, are truncated to zero. The following +scaling units are accepted: +. +.Pp +.Bl -tag -width Ds -offset indent -compact +.It c +centimetre +.It i +inch +.It P +pica (~1/6 inch) +.It p +point (~1/72 inch) +.It f +synonym for +.Sq u +.It v +default vertical span +.It m +width of rendered +.Sq m +.Pq em +character +.It n +width of rendered +.Sq n +.Pq en +character +.It u +default horizontal span +.It M +mini-em (~1/100 em) +.El +.Pp +Using anything other than +.Sq m , +.Sq n , +.Sq u , +or +.Sq v +is necessarily non-portable across output media. See +.Sx COMPATIBILITY . +. +.Pp +If a scaling unit is not provided, the numerical value is interpreted +under the default rules of +.Sq v +for vertical spaces and +.Sq u +for horizontal ones. +.Em Note : +this differs from +.Xr mdoc 7 , +which, if a unit is not provided, will instead interpret the string as +literal text. +. +. +.Sh MANUAL STRUCTURE +Each +.Nm +document must contain contains at least the +.Sx \&TH +macro describing the document's section and title. It may occur +anywhere in the document, although conventionally, it appears as the +first macro. +. +.Pp +Beyond +.Sx \&TH , +at least one macro or text node must appear in the document. Documents +are generally structured as follows: +.Bd -literal -offset indent +\&.TH FOO 1 "13 Aug 2009" +\&. +\&.SH NAME +\efBfoo\efR \e(en a description goes here +\&.\e\*q The next is for sections 2 & 3 only. +\&.\e\*q .SH LIBRARY +\&. +\&.SH SYNOPSIS +\efBfoo\efR [\efB\e-options\efR] arguments... +\&. +\&.SH DESCRIPTION +The \efBfoo\efR utility processes files... +\&. +\&.\e\*q .SH IMPLEMENTATION NOTES +\&.\e\*q The next is for sections 1 & 8 only. +\&.\e\*q .SH EXIT STATUS +\&.\e\*q The next is for sections 2, 3, & 9 only. +\&.\e\*q .SH RETURN VALUES +\&.\e\*q The next is for sections 1, 6, 7, & 8 only. +\&.\e\*q .SH ENVIRONMENT +\&.\e\*q .SH FILES +\&.\e\*q .SH EXAMPLES +\&.\e\*q The next is for sections 1, 4, 6, 7, & 8 only. +\&.\e\*q .SH DIAGNOSTICS +\&.\e\*q The next is for sections 2, 3, & 9 only. +\&.\e\*q .SH ERRORS +\&.\e\*q .SH SEE ALSO +\&.\e\*q \efBbar\efR(1) +\&.\e\*q .SH STANDARDS +\&.\e\*q .SH HISTORY +\&.\e\*q .SH AUTHORS +\&.\e\*q .SH CAVEATS +\&.\e\*q .SH BUGS +\&.\e\*q .SH SECURITY CONSIDERATIONS +.Ed +. +. +.Sh MACRO SYNTAX +Macros are one to three three characters in length and begin with a +control character , +.Sq \&. , +at the beginning of the line. An arbitrary amount of whitespace may +sit between the control character and the macro name. Thus, the +following are equivalent: +.Bd -literal -offset indent +\&.PP +\&.\ \ \ PP +.Ed +. +.Pp +The +.Nm +macros are classified by scope: line scope or block scope. Line +macros are only scoped to the current line (and, in some situations, +the subsequent line). Block macros are scoped to the current line and +subsequent lines until closed by another block macro. +. +. +.Ss Line Macros +Line macros are generally scoped to the current line, with the body +consisting of zero or more arguments. If a macro is scoped to the next +line and the line arguments are empty, the next line is used instead, +else the general syntax is used. Thus: +.Bd -literal -offset indent +\&.I +foo +.Ed +. +.Pp +is equivalent to +.Sq \&.I foo . +If next-line macros are invoked consecutively, only the last is used. +If a next-line macro is proceded by a block macro, it is ignored. +.Bd -literal -offset indent +\&.YO \(lBbody...\(rB +\(lBbody...\(rB +.Ed +. +.Pp +.Bl -column -compact -offset indent "MacroX" "ArgumentsX" "ScopeXXXXX" +.It Em Macro Ta Em Arguments Ta Em Scope +.It Sx \&B Ta n Ta next-line +.It Sx \&BI Ta n Ta current +.It Sx \&BR Ta n Ta current +.It Sx \&DT Ta 0 Ta current +.It Sx \&I Ta n Ta next-line +.It Sx \&IB Ta n Ta current +.It Sx \&IR Ta n Ta current +.It Sx \&R Ta n Ta next-line +.It Sx \&RB Ta n Ta current +.It Sx \&RI Ta n Ta current +.It Sx \&SB Ta n Ta next-line +.It Sx \&SM Ta n Ta next-line +.It Sx \&TH Ta >1, <6 Ta current +.It Sx \&UC Ta n Ta current +.It Sx \&br Ta 0 Ta current +.It Sx \&fi Ta 0 Ta current +.It Sx \&i Ta n Ta current +.It Sx \&na Ta 0 Ta current +.It Sx \&nf Ta 0 Ta current +.It Sx \&r Ta 0 Ta current +.It Sx \&sp Ta 1 Ta current +.El +. +.Pp +The +.Sx \&RS , +.Sx \&RE , +.Sx \&UC , +.Sx \&br , +.Sx \&fi , +.Sx \&i , +.Sx \&na , +.Sx \&nf , +.Sx \&r , +and +.Sx \&sp +macros should not be used. They're included for compatibility. +. +. +.Ss Block Macros +Block macros are comprised of a head and body. Like for in-line macros, +the head is scoped to the current line and, in one circumstance, the +next line; the body is scoped to subsequent lines and is closed out by a +subsequent block macro invocation. +.Bd -literal -offset indent +\&.YO \(lBhead...\(rB +\(lBhead...\(rB +\(lBbody...\(rB +.Ed +. +.Pp +The closure of body scope may be to the section, where a macro is closed +by +.Sx \&SH ; +sub-section, closed by a section or +.Sx \&SS ; +part, closed by a section, sub-section, or +.Sx \&RE ; +or paragraph, closed by a section, sub-section, part, +.Sx \&HP , +.Sx \&IP , +.Sx \&LP , +.Sx \&P , +.Sx \&PP , +or +.Sx \&TP . +No closure refers to an explicit block closing macro. +. +.Pp +.Bl -column "MacroX" "ArgumentsX" "Head ScopeX" "sub-sectionX" -compact -offset indent +.It Em Macro Ta Em Arguments Ta Em Head Scope Ta Em Body Scope +.It Sx \&HP Ta <2 Ta current Ta paragraph +.It Sx \&IP Ta <3 Ta current Ta paragraph +.It Sx \&LP Ta 0 Ta current Ta paragraph +.It Sx \&P Ta 0 Ta current Ta paragraph +.It Sx \&PP Ta 0 Ta current Ta paragraph +.It Sx \&RE Ta 0 Ta current Ta none +.It Sx \&RS Ta 1 Ta current Ta part +.It Sx \&SH Ta >0 Ta next-line Ta section +.It Sx \&SS Ta >0 Ta next-line Ta sub-section +.It Sx \&TP Ta n Ta next-line Ta paragraph +.El +. +.Pp +If a block macro is next-line scoped, it may only be followed by in-line +macros (excluding +.Sx \&DT , +.Sx \&TH , +.Sx \&UC , +.Sx \&br , +.Sx \&na , +.Sx \&sp , +.Sx \&nf , +and +.Sx \&fi ) . +. +. +.Sh REFERENCE +This section is a canonical reference to all macros, arranged +alphabetically. For the scoping of individual macros, see +.Sx MACRO SYNTAX . +. +.Ss \&B +Text is rendered in bold face. +.Ss \&BI +Text is rendered alternately in bold face and italic. Thus, +.Sq .BI this word and that +causes +.Sq this +and +.Sq and +to render in bold face, while +.Sq word +and +.Sq that +render in italics. Whitespace between arguments is omitted in output. +.Ss \&BR +Text is rendered alternately in bold face and roman (the default font). +Whitespace between arguments is omitted in output. +.Ss \&DT +Has no effect. Included for compatibility. +.Ss \&HP +Begin a paragraph whose initial output line is left-justified, but +subsequent output lines are indented, with the following syntax: +.Bd -literal -offset indent +\&.HP [width] +.Ed +. +.Pp +If scaling width +.Va width +is specified, it's saved for later paragraph left-margins; if +unspecified, the saved or default width is used. +.Ss \&I +Text is rendered in italics. +.Ss \&IB +Text is rendered alternately in italics and bold face. Whitespace +between arguments is omitted in output. +.Ss \&IP +Begin a paragraph with the following syntax: +.Bd -literal -offset indent +\&.IP [head [width]] +.Ed +. +.Pp +This follows the behaviour of the +.Sx \&TP +except for the macro syntax (all arguments on the line, instead of +having next-line scope). If +.Va width +is specified, it's saved for later paragraph left-margins; if +unspecified, the saved or default width is used. +.Ss \&IR +Text is rendered alternately in italics and roman (the default font). +Whitespace between arguments is omitted in output. +.Ss \&LP +Begin an undecorated paragraph. The scope of a paragraph is closed by a +subsequent paragraph, sub-section, section, or end of file. The saved +paragraph left-margin width is re-set to the default. +.Ss \&P +Synonym for +.Sx \&LP . +.Ss \&PP +Synonym for +.Sx \&LP . +.Ss \&R +Text is rendered in roman (the default font). +.Ss \&RB +Text is rendered alternately in roman (the default font) and bold face. +Whitespace between arguments is omitted in output. +.Ss \&RE +Explicitly close out the scope of a prior +.Sx \&RS . +.Ss \&RI +Text is rendered alternately in roman (the default font) and italics. +Whitespace between arguments is omitted in output. +.Ss \&RS +Begin a part setting the left margin. The left margin controls the +offset, following an initial indentation, to un-indented text such as +that of +.Sx \&PP . +A scaling width may be specified as following: +.Bd -literal -offset indent +\&.RS [width] +.Ed +. +.Pp +If +.Va width +is not specified, the saved or default width is used. +.Ss \&SB +Text is rendered in small size (one point smaller than the default font) +bold face. +.Ss \&SH +Begin a section. The scope of a section is only closed by another +section or the end of file. The paragraph left-margin width is re-set +to the default. +.Ss \&SM +Text is rendered in small size (one point smaller than the default +font). +.Ss \&SS +Begin a sub-section. The scope of a sub-section is closed by a +subsequent sub-section, section, or end of file. The paragraph +left-margin width is re-set to the default. +.Ss \&TH +Sets the title of the manual page with the following syntax: +.Bd -literal -offset indent +\&.TH title section [date [source [volume]]] +.Ed +. +.Pp +At least the +.Va title +and +.Va section +arguments must be provided. The +.Va date +argument should be formatted as +.Qq %b [%d] %Y +format, described in +.Xr strptime 3 . +The +.Va source +string specifies the organisation providing the utility. The +.Va volume +replaces the default rendered volume as dictated by the manual section. +.Ss \&TP +Begin a paragraph where the head, if exceeding the indentation width, is +followed by a newline; if not, the body follows on the same line after a +buffer to the indentation width. Subsequent output lines are indented. +. +.Pp +The indentation scaling width may be set as follows: +.Bd -literal -offset indent +\&.TP [width] +.Ed +. +.Pp +If +.Va width +is specified, it's saved for later paragraph left-margins; if +unspecified, the saved or default width is used. +.Ss \&UC +Has no effect. Included for compatibility. +.Ss \&br +Breaks the current line. Consecutive invocations have no further effect. +.Ss \&fi +End literal mode begun by +.Sx \&nf . +.Ss \&i +Italicise arguments. If no arguments are specified, all subsequent text +is italicised. +.Ss \&na +Don't align to the right margin. +.Ss \&nf +Begin literal mode: all subsequent free-form lines have their end of +line boundaries preserved. May be ended by +.Sx \&fi . +.Ss \&r +Fonts and styles (bold face, italics) reset to roman (default font). +.Ss \&sp +Insert n spaces, where n is the macro's positive numeric argument. If +0, this is equivalent to the +.Sx \&br +macro. +. +. +.Sh COMPATIBILITY +This section documents compatibility with other roff implementations, at +this time limited to +.Xr groff 1 . +.Bl -hyphen +.It +In quoted literals, groff allowed pair-wise double-quotes to produce a +standalone double-quote in formatted output. This idiosyncratic +behaviour is no longer applicable. +.It +The +.Sq sp +macro does not accept negative numbers. +.It +Blocks of whitespace are stripped from both macro and free-form text +lines (except when in literal mode), while groff would retain whitespace +in free-form text lines. +.El +. +. +.Sh SEE ALSO +.Xr mandoc 1 , +.Xr mandoc_char 7 +. +. +.Sh AUTHORS +The +.Nm +reference was written by +.An Kristaps Dzonsons Aq kristaps@kth.se . +. +. +.Sh CAVEATS +Do not use this language. Use +.Xr mdoc 7 , +instead. +. diff --git a/usr.bin/mandoc/man.c b/usr.bin/mandoc/man.c new file mode 100644 index 0000000000..5356635fd4 --- /dev/null +++ b/usr.bin/mandoc/man.c @@ -0,0 +1,628 @@ +/* $Id: man.c,v 1.14 2009/10/19 10:20:24 schwarze Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include + +#include +#include +#include +#include +#include +#include + +#include "libman.h" + +const char *const __man_merrnames[WERRMAX] = { + "invalid character", /* WNPRINT */ + "system: malloc error", /* WNMEM */ + "invalid manual section", /* WMSEC */ + "invalid date format", /* WDATE */ + "scope of prior line violated", /* WLNSCOPE */ + "trailing whitespace", /* WTSPACE */ + "unterminated quoted parameter", /* WTQUOTE */ + "document has no body", /* WNODATA */ + "document has no title/section", /* WNOTITLE */ + "invalid escape sequence", /* WESCAPE */ + "invalid number format", /* WNUMFMT */ + "expected block head arguments", /* WHEADARGS */ + "expected block body arguments", /* WBODYARGS */ + "expected empty block head", /* WNHEADARGS */ + "unknown macro", /* WMACRO */ + "ill-formed macro", /* WMACROFORM */ + "scope open on exit", /* WEXITSCOPE */ + "no scope context", /* WNOSCOPE */ + "literal context already open", /* WOLITERAL */ + "no literal context open" /* WNLITERAL */ +}; + +const char *const __man_macronames[MAN_MAX] = { + "br", "TH", "SH", "SS", + "TP", "LP", "PP", "P", + "IP", "HP", "SM", "SB", + "BI", "IB", "BR", "RB", + "R", "B", "I", "IR", + "RI", "na", "i", "sp", + "nf", "fi", "r", "RE", + "RS", "DT", "UC" + }; + +const char * const *man_macronames = __man_macronames; + +static struct man_node *man_node_alloc(int, int, + enum man_type, int); +static int man_node_append(struct man *, + struct man_node *); +static int man_ptext(struct man *, int, char *); +static int man_pmacro(struct man *, int, char *); +static void man_free1(struct man *); +static int man_alloc1(struct man *); +static int pstring(struct man *, int, int, + const char *, size_t); + + +const struct man_node * +man_node(const struct man *m) +{ + + return(MAN_HALT & m->flags ? NULL : m->first); +} + + +const struct man_meta * +man_meta(const struct man *m) +{ + + return(MAN_HALT & m->flags ? NULL : &m->meta); +} + + +int +man_reset(struct man *man) +{ + + man_free1(man); + return(man_alloc1(man)); +} + + +void +man_free(struct man *man) +{ + + man_free1(man); + free(man); +} + + +struct man * +man_alloc(void *data, int pflags, const struct man_cb *cb) +{ + struct man *p; + + if (NULL == (p = calloc(1, sizeof(struct man)))) + return(NULL); + + if ( ! man_alloc1(p)) { + free(p); + return(NULL); + } + + man_hash_init(); + + p->data = data; + p->pflags = pflags; + (void)memcpy(&p->cb, cb, sizeof(struct man_cb)); + return(p); +} + + +int +man_endparse(struct man *m) +{ + + if (MAN_HALT & m->flags) + return(0); + else if (man_macroend(m)) + return(1); + m->flags |= MAN_HALT; + return(0); +} + + +int +man_parseln(struct man *m, int ln, char *buf) +{ + + return('.' == *buf ? + man_pmacro(m, ln, buf) : + man_ptext(m, ln, buf)); +} + + +static void +man_free1(struct man *man) +{ + + if (man->first) + man_node_freelist(man->first); + if (man->meta.title) + free(man->meta.title); + if (man->meta.source) + free(man->meta.source); + if (man->meta.vol) + free(man->meta.vol); +} + + +static int +man_alloc1(struct man *m) +{ + + bzero(&m->meta, sizeof(struct man_meta)); + m->flags = 0; + m->last = calloc(1, sizeof(struct man_node)); + if (NULL == m->last) + return(0); + m->first = m->last; + m->last->type = MAN_ROOT; + m->next = MAN_NEXT_CHILD; + return(1); +} + + +static int +man_node_append(struct man *man, struct man_node *p) +{ + + assert(man->last); + assert(man->first); + assert(MAN_ROOT != p->type); + + switch (man->next) { + case (MAN_NEXT_SIBLING): + man->last->next = p; + p->prev = man->last; + p->parent = man->last->parent; + break; + case (MAN_NEXT_CHILD): + man->last->child = p; + p->parent = man->last; + break; + default: + abort(); + /* NOTREACHED */ + } + + p->parent->nchild++; + + if ( ! man_valid_pre(man, p)) + return(0); + + switch (p->type) { + case (MAN_HEAD): + assert(MAN_BLOCK == p->parent->type); + p->parent->head = p; + break; + case (MAN_BODY): + assert(MAN_BLOCK == p->parent->type); + p->parent->body = p; + break; + default: + break; + } + + man->last = p; + + switch (p->type) { + case (MAN_TEXT): + if ( ! man_valid_post(man)) + return(0); + if ( ! man_action_post(man)) + return(0); + break; + default: + break; + } + + return(1); +} + + +static struct man_node * +man_node_alloc(int line, int pos, enum man_type type, int tok) +{ + struct man_node *p; + + p = calloc(1, sizeof(struct man_node)); + if (NULL == p) + return(NULL); + + p->line = line; + p->pos = pos; + p->type = type; + p->tok = tok; + return(p); +} + + +int +man_elem_alloc(struct man *m, int line, int pos, int tok) +{ + struct man_node *p; + + p = man_node_alloc(line, pos, MAN_ELEM, tok); + if (NULL == p) + return(0); + if ( ! man_node_append(m, p)) + return(0); + m->next = MAN_NEXT_CHILD; + return(1); +} + + +int +man_head_alloc(struct man *m, int line, int pos, int tok) +{ + struct man_node *p; + + p = man_node_alloc(line, pos, MAN_HEAD, tok); + if (NULL == p) + return(0); + if ( ! man_node_append(m, p)) + return(0); + m->next = MAN_NEXT_CHILD; + return(1); +} + + +int +man_body_alloc(struct man *m, int line, int pos, int tok) +{ + struct man_node *p; + + p = man_node_alloc(line, pos, MAN_BODY, tok); + if (NULL == p) + return(0); + if ( ! man_node_append(m, p)) + return(0); + m->next = MAN_NEXT_CHILD; + return(1); +} + + +int +man_block_alloc(struct man *m, int line, int pos, int tok) +{ + struct man_node *p; + + p = man_node_alloc(line, pos, MAN_BLOCK, tok); + if (NULL == p) + return(0); + if ( ! man_node_append(m, p)) + return(0); + m->next = MAN_NEXT_CHILD; + return(1); +} + + +static int +pstring(struct man *m, int line, int pos, + const char *p, size_t len) +{ + struct man_node *n; + size_t sv; + + n = man_node_alloc(line, pos, MAN_TEXT, -1); + if (NULL == n) + return(0); + + n->string = malloc(len + 1); + if (NULL == n->string) { + free(n); + return(0); + } + + sv = strlcpy(n->string, p, len + 1); + + /* Prohibit truncation. */ + assert(sv < len + 1); + + if ( ! man_node_append(m, n)) + return(0); + m->next = MAN_NEXT_SIBLING; + return(1); +} + + +int +man_word_alloc(struct man *m, int line, int pos, const char *word) +{ + + return(pstring(m, line, pos, word, strlen(word))); +} + + +void +man_node_free(struct man_node *p) +{ + + if (p->string) + free(p->string); + if (p->parent) + p->parent->nchild--; + free(p); +} + + +void +man_node_freelist(struct man_node *p) +{ + struct man_node *n; + + if (p->child) + man_node_freelist(p->child); + assert(0 == p->nchild); + n = p->next; + man_node_free(p); + if (n) + man_node_freelist(n); +} + + +static int +man_ptext(struct man *m, int line, char *buf) +{ + int i, j; + + /* Literal free-form text whitespace is preserved. */ + + if (MAN_LITERAL & m->flags) { + if ( ! man_word_alloc(m, line, 0, buf)) + return(0); + goto descope; + } + + /* First de-chunk and allocate words. */ + + for (i = 0; ' ' == buf[i]; i++) + /* Skip leading whitespace. */ ; + if (0 == buf[i]) { + if ( ! pstring(m, line, 0, &buf[i], 0)) + return(0); + goto descope; + } + + for (j = i; buf[i]; i++) { + if (' ' != buf[i]) + continue; + + /* Escaped whitespace. */ + if (i && ' ' == buf[i] && '\\' == buf[i - 1]) + continue; + + buf[i++] = 0; + if ( ! pstring(m, line, j, &buf[j], (size_t)(i - j))) + return(0); + + for ( ; ' ' == buf[i]; i++) + /* Skip trailing whitespace. */ ; + + j = i; + if (0 == buf[i]) + break; + } + + if (j != i && ! pstring(m, line, j, &buf[j], (size_t)(i - j))) + return(0); + +descope: + + /* + * Co-ordinate what happens with having a next-line scope open: + * first close out the element scope (if applicable), then close + * out the block scope (also if applicable). + */ + + if (MAN_ELINE & m->flags) { + m->flags &= ~MAN_ELINE; + if ( ! man_unscope(m, m->last->parent)) + return(0); + } + + if ( ! (MAN_BLINE & m->flags)) + return(1); + m->flags &= ~MAN_BLINE; + + if ( ! man_unscope(m, m->last->parent)) + return(0); + return(man_body_alloc(m, line, 0, m->last->tok)); +} + + +int +man_pmacro(struct man *m, int ln, char *buf) +{ + int i, j, c, ppos, fl; + char mac[5]; + struct man_node *n; + + /* Comments and empties are quickly ignored. */ + + fl = m->flags; + + if (0 == buf[1]) + goto out; + + i = 1; + + if (' ' == buf[i]) { + i++; + while (buf[i] && ' ' == buf[i]) + i++; + if (0 == buf[i]) + goto out; + } + + ppos = i; + + /* Copy the first word into a nil-terminated buffer. */ + + for (j = 0; j < 4; j++, i++) { + if (0 == (mac[j] = buf[i])) + break; + else if (' ' == buf[i]) + break; + + /* Check for invalid characters. */ + + if (isgraph((u_char)buf[i])) + continue; + return(man_perr(m, ln, i, WNPRINT)); + } + + mac[j] = 0; + + if (j == 4 || j < 1) { + if ( ! (MAN_IGN_MACRO & m->pflags)) { + (void)man_perr(m, ln, ppos, WMACROFORM); + goto err; + } + if ( ! man_pwarn(m, ln, ppos, WMACROFORM)) + goto err; + return(1); + } + + if (MAN_MAX == (c = man_hash_find(mac))) { + if ( ! (MAN_IGN_MACRO & m->pflags)) { + (void)man_perr(m, ln, ppos, WMACRO); + goto err; + } + if ( ! man_pwarn(m, ln, ppos, WMACRO)) + goto err; + return(1); + } + + /* The macro is sane. Jump to the next word. */ + + while (buf[i] && ' ' == buf[i]) + i++; + + /* Remove prior ELINE macro, if applicable. */ + + if (m->flags & MAN_ELINE) { + n = m->last; + assert(NULL == n->child); + assert(0 == n->nchild); + if ( ! man_nwarn(m, n, WLNSCOPE)) + return(0); + + if (n->prev) { + assert(n != n->parent->child); + assert(n == n->prev->next); + n->prev->next = NULL; + m->last = n->prev; + m->next = MAN_NEXT_SIBLING; + } else { + assert(n == n->parent->child); + n->parent->child = NULL; + m->last = n->parent; + m->next = MAN_NEXT_CHILD; + } + + man_node_free(n); + m->flags &= ~MAN_ELINE; + } + + /* Begin recursive parse sequence. */ + + assert(man_macros[c].fp); + + if ( ! (*man_macros[c].fp)(m, c, ln, ppos, &i, buf)) + goto err; + +out: + if ( ! (MAN_BLINE & fl)) + return(1); + + /* + * If we've opened a new next-line element scope, then return + * now, as the next line will close out the block scope. + */ + + if (MAN_ELINE & m->flags) + return(1); + + /* Close out the block scope opened in the prior line. */ + + assert(MAN_BLINE & m->flags); + m->flags &= ~MAN_BLINE; + + if ( ! man_unscope(m, m->last->parent)) + return(0); + return(man_body_alloc(m, ln, 0, m->last->tok)); + +err: /* Error out. */ + + m->flags |= MAN_HALT; + return(0); +} + + +int +man_verr(struct man *man, int ln, int pos, const char *fmt, ...) +{ + char buf[256]; + va_list ap; + + if (NULL == man->cb.man_err) + return(0); + + va_start(ap, fmt); + (void)vsnprintf(buf, sizeof(buf) - 1, fmt, ap); + va_end(ap); + return((*man->cb.man_err)(man->data, ln, pos, buf)); +} + + +int +man_vwarn(struct man *man, int ln, int pos, const char *fmt, ...) +{ + char buf[256]; + va_list ap; + + if (NULL == man->cb.man_warn) + return(0); + + va_start(ap, fmt); + (void)vsnprintf(buf, sizeof(buf) - 1, fmt, ap); + va_end(ap); + return((*man->cb.man_warn)(man->data, ln, pos, buf)); +} + + +int +man_err(struct man *m, int line, int pos, int iserr, enum merr type) +{ + const char *p; + + p = __man_merrnames[(int)type]; + assert(p); + + if (iserr) + return(man_verr(m, line, pos, p)); + + return(man_vwarn(m, line, pos, p)); +} diff --git a/usr.bin/mandoc/man.h b/usr.bin/mandoc/man.h new file mode 100644 index 0000000000..deb7b897e3 --- /dev/null +++ b/usr.bin/mandoc/man.h @@ -0,0 +1,116 @@ +/* $Id: man.h,v 1.10 2009/10/19 21:08:58 schwarze Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef MAN_H +#define MAN_H + +#include + +#define MAN_br 0 +#define MAN_TH 1 +#define MAN_SH 2 +#define MAN_SS 3 +#define MAN_TP 4 +#define MAN_LP 5 +#define MAN_PP 6 +#define MAN_P 7 +#define MAN_IP 8 +#define MAN_HP 9 +#define MAN_SM 10 +#define MAN_SB 11 +#define MAN_BI 12 +#define MAN_IB 13 +#define MAN_BR 14 +#define MAN_RB 15 +#define MAN_R 16 +#define MAN_B 17 +#define MAN_I 18 +#define MAN_IR 19 +#define MAN_RI 20 +#define MAN_na 21 +#define MAN_i 22 +#define MAN_sp 23 +#define MAN_nf 24 +#define MAN_fi 25 +#define MAN_r 26 +#define MAN_RE 27 +#define MAN_RS 28 +#define MAN_DT 29 +#define MAN_UC 30 +#define MAN_MAX 31 + +enum man_type { + MAN_TEXT, + MAN_ELEM, + MAN_ROOT, + MAN_BLOCK, + MAN_HEAD, + MAN_BODY +}; + +struct man_meta { + int msec; + time_t date; + char *vol; + char *title; + char *source; +}; + +struct man_node { + struct man_node *parent; + struct man_node *child; + struct man_node *next; + struct man_node *prev; + int nchild; + int line; + int pos; + int tok; + int flags; +#define MAN_VALID (1 << 0) +#define MAN_ACTED (1 << 1) + enum man_type type; + char *string; + struct man_node *head; + struct man_node *body; +}; + +#define MAN_IGN_MACRO (1 << 0) +#define MAN_IGN_CHARS (1 << 1) +#define MAN_IGN_ESCAPE (1 << 2) + +extern const char *const *man_macronames; + +struct man_cb { + int (*man_warn)(void *, int, int, const char *); + int (*man_err)(void *, int, int, const char *); +}; + +__BEGIN_DECLS + +struct man; + +void man_free(struct man *); +struct man *man_alloc(void *, int, const struct man_cb *); +int man_reset(struct man *); +int man_parseln(struct man *, int, char *buf); +int man_endparse(struct man *); + +const struct man_node *man_node(const struct man *); +const struct man_meta *man_meta(const struct man *); + +__END_DECLS + +#endif /*!MAN_H*/ diff --git a/usr.bin/mandoc/man_action.c b/usr.bin/mandoc/man_action.c new file mode 100644 index 0000000000..a7870ad575 --- /dev/null +++ b/usr.bin/mandoc/man_action.c @@ -0,0 +1,219 @@ +/* $Id: man_action.c,v 1.8 2009/09/18 22:46:14 schwarze Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include + +#include +#include +#include +#include + +#include "libman.h" + +struct actions { + int (*post)(struct man *); +}; + +static int post_TH(struct man *); +static int post_fi(struct man *); +static int post_nf(struct man *); + +const struct actions man_actions[MAN_MAX] = { + { NULL }, /* br */ + { post_TH }, /* TH */ + { NULL }, /* SH */ + { NULL }, /* SS */ + { NULL }, /* TP */ + { NULL }, /* LP */ + { NULL }, /* PP */ + { NULL }, /* P */ + { NULL }, /* IP */ + { NULL }, /* HP */ + { NULL }, /* SM */ + { NULL }, /* SB */ + { NULL }, /* BI */ + { NULL }, /* IB */ + { NULL }, /* BR */ + { NULL }, /* RB */ + { NULL }, /* R */ + { NULL }, /* B */ + { NULL }, /* I */ + { NULL }, /* IR */ + { NULL }, /* RI */ + { NULL }, /* na */ + { NULL }, /* i */ + { NULL }, /* sp */ + { post_nf }, /* nf */ + { post_fi }, /* fi */ + { NULL }, /* r */ + { NULL }, /* RE */ + { NULL }, /* RS */ + { NULL }, /* DT */ + { NULL }, /* UC */ +}; + +static time_t man_atotime(const char *); + + +int +man_action_post(struct man *m) +{ + + if (MAN_ACTED & m->last->flags) + return(1); + m->last->flags |= MAN_ACTED; + + switch (m->last->type) { + case (MAN_TEXT): + /* FALLTHROUGH */ + case (MAN_ROOT): + return(1); + default: + break; + } + + if (NULL == man_actions[m->last->tok].post) + return(1); + return((*man_actions[m->last->tok].post)(m)); +} + + +static int +post_fi(struct man *m) +{ + + if ( ! (MAN_LITERAL & m->flags)) + if ( ! man_nwarn(m, m->last, WNLITERAL)) + return(0); + m->flags &= ~MAN_LITERAL; + return(1); +} + + +static int +post_nf(struct man *m) +{ + + if (MAN_LITERAL & m->flags) + if ( ! man_nwarn(m, m->last, WOLITERAL)) + return(0); + m->flags |= MAN_LITERAL; + return(1); +} + + +static int +post_TH(struct man *m) +{ + struct man_node *n; + char *ep; + long lval; + + if (m->meta.title) + free(m->meta.title); + if (m->meta.vol) + free(m->meta.vol); + if (m->meta.source) + free(m->meta.source); + + m->meta.title = m->meta.vol = m->meta.source = NULL; + m->meta.msec = 0; + m->meta.date = 0; + + /* ->TITLE<- MSEC DATE SOURCE VOL */ + + n = m->last->child; + assert(n); + + if (NULL == (m->meta.title = strdup(n->string))) + return(man_nerr(m, n, WNMEM)); + + /* TITLE ->MSEC<- DATE SOURCE VOL */ + + n = n->next; + assert(n); + + errno = 0; + lval = strtol(n->string, &ep, 10); + if (n->string[0] != '\0' && *ep == '\0') + m->meta.msec = (int)lval; + else if ( ! man_nwarn(m, n, WMSEC)) + return(0); + + /* TITLE MSEC ->DATE<- SOURCE VOL */ + + if (NULL == (n = n->next)) + m->meta.date = time(NULL); + else if (0 == (m->meta.date = man_atotime(n->string))) { + if ( ! man_nwarn(m, n, WDATE)) + return(0); + m->meta.date = time(NULL); + } + + /* TITLE MSEC DATE ->SOURCE<- VOL */ + + if (n && (n = n->next)) + if (NULL == (m->meta.source = strdup(n->string))) + return(man_nerr(m, n, WNMEM)); + + /* TITLE MSEC DATE SOURCE ->VOL<- */ + + if (n && (n = n->next)) + if (NULL == (m->meta.vol = strdup(n->string))) + return(man_nerr(m, n, WNMEM)); + + /* + * The end document shouldn't have the prologue macros as part + * of the syntax tree (they encompass only meta-data). + */ + + if (m->last->parent->child == m->last) { + m->last->parent->child = NULL; + n = m->last; + m->last = m->last->parent; + m->next = MAN_NEXT_CHILD; + } else { + assert(m->last->prev); + m->last->prev->next = NULL; + n = m->last; + m->last = m->last->prev; + m->next = MAN_NEXT_SIBLING; + } + + man_node_freelist(n); + return(1); +} + + +static time_t +man_atotime(const char *p) +{ + struct tm tm; + char *pp; + + bzero(&tm, sizeof(struct tm)); + + if ((pp = strptime(p, "%b %d %Y", &tm)) && 0 == *pp) + return(mktime(&tm)); + if ((pp = strptime(p, "%d %b %Y", &tm)) && 0 == *pp) + return(mktime(&tm)); + if ((pp = strptime(p, "%b %d, %Y", &tm)) && 0 == *pp) + return(mktime(&tm)); + if ((pp = strptime(p, "%b %Y", &tm)) && 0 == *pp) + return(mktime(&tm)); + + return(0); +} diff --git a/usr.bin/mandoc/man_argv.c b/usr.bin/mandoc/man_argv.c new file mode 100644 index 0000000000..6d5b35bc25 --- /dev/null +++ b/usr.bin/mandoc/man_argv.c @@ -0,0 +1,98 @@ +/* $Id: man_argv.c,v 1.1 2009/08/23 11:22:19 schwarze Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include + +#include +#include +#include + +#include "libman.h" + + +int +man_args(struct man *m, int line, int *pos, char *buf, char **v) +{ + + assert(*pos); + assert(' ' != buf[*pos]); + + if (0 == buf[*pos]) + return(ARGS_EOLN); + + *v = &buf[*pos]; + + /* + * Process a quoted literal. A quote begins with a double-quote + * and ends with a double-quote NOT preceded by a double-quote. + * Whitespace is NOT involved in literal termination. + */ + + if ('\"' == buf[*pos]) { + *v = &buf[++(*pos)]; + + for ( ; buf[*pos]; (*pos)++) { + if ('\"' != buf[*pos]) + continue; + if ('\"' != buf[*pos + 1]) + break; + (*pos)++; + } + + if (0 == buf[*pos]) { + if ( ! man_pwarn(m, line, *pos, WTQUOTE)) + return(ARGS_ERROR); + return(ARGS_QWORD); + } + + buf[(*pos)++] = 0; + + if (0 == buf[*pos]) + return(ARGS_QWORD); + + while (' ' == buf[*pos]) + (*pos)++; + + if (0 == buf[*pos]) + if ( ! man_pwarn(m, line, *pos, WTSPACE)) + return(ARGS_ERROR); + + return(ARGS_QWORD); + } + + /* + * A non-quoted term progresses until either the end of line or + * a non-escaped whitespace. + */ + + for ( ; buf[*pos]; (*pos)++) + if (' ' == buf[*pos] && '\\' != buf[*pos - 1]) + break; + + if (0 == buf[*pos]) + return(ARGS_WORD); + + buf[(*pos)++] = 0; + + while (' ' == buf[*pos]) + (*pos)++; + + if (0 == buf[*pos]) + if ( ! man_pwarn(m, line, *pos, WTSPACE)) + return(ARGS_ERROR); + + return(ARGS_WORD); +} diff --git a/usr.bin/mandoc/man_hash.c b/usr.bin/mandoc/man_hash.c new file mode 100644 index 0000000000..506f997b90 --- /dev/null +++ b/usr.bin/mandoc/man_hash.c @@ -0,0 +1,77 @@ +/* $Id: man_hash.c,v 1.7 2009/10/19 10:20:24 schwarze Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include + +#include +#include +#include +#include + +#include "libman.h" + +static u_char table[26 * 6]; + +/* + * XXX - this hash has global scope, so if intended for use as a library + * with multiple callers, it will need re-invocation protection. + */ +void +man_hash_init(void) +{ + int i, j, x; + + memset(table, UCHAR_MAX, sizeof(table)); + + for (i = 0; i < MAN_MAX; i++) { + x = man_macronames[i][0]; + assert((x >= 65 && x <= 90) || + (x >= 97 && x <= 122)); + + x -= (x <= 90) ? 65 : 97; + x *= 6; + + for (j = 0; j < 6; j++) + if (UCHAR_MAX == table[x + j]) { + table[x + j] = (u_char)i; + break; + } + assert(j < 6); + } +} + +int +man_hash_find(const char *tmp) +{ + int x, i, tok; + + if (0 == (x = tmp[0])) + return(MAN_MAX); + if ( ! ((x >= 65 && x <= 90) || (x >= 97 && x <= 122))) + return(MAN_MAX); + + x -= (x <= 90) ? 65 : 97; + x *= 6; + + for (i = 0; i < 6; i++) { + if (UCHAR_MAX == (tok = table[x + i])) + return(MAN_MAX); + if (0 == strcmp(tmp, man_macronames[tok])) + return(tok); + } + + return(MAN_MAX); +} diff --git a/usr.bin/mandoc/man_html.c b/usr.bin/mandoc/man_html.c new file mode 100644 index 0000000000..63152bcd95 --- /dev/null +++ b/usr.bin/mandoc/man_html.c @@ -0,0 +1,696 @@ +/* $Id: man_html.c,v 1.1 2009/10/21 19:13:50 schwarze Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "out.h" +#include "html.h" +#include "man.h" +#include "main.h" + +/* TODO: preserve ident widths. */ + +#define INDENT 5 +#define HALFINDENT 3 + +#define MAN_ARGS const struct man_meta *m, \ + const struct man_node *n, \ + struct html *h + +struct htmlman { + int (*pre)(MAN_ARGS); + int (*post)(MAN_ARGS); +}; + +static void print_man(MAN_ARGS); +static void print_man_head(MAN_ARGS); +static void print_man_nodelist(MAN_ARGS); +static void print_man_node(MAN_ARGS); + +static int a2width(const struct man_node *, + struct roffsu *); + +static int man_alt_pre(MAN_ARGS); +static int man_br_pre(MAN_ARGS); +static int man_ign_pre(MAN_ARGS); +static void man_root_post(MAN_ARGS); +static int man_root_pre(MAN_ARGS); +static int man_B_pre(MAN_ARGS); +static int man_HP_pre(MAN_ARGS); +static int man_I_pre(MAN_ARGS); +static int man_IP_pre(MAN_ARGS); +static int man_PP_pre(MAN_ARGS); +static int man_RS_pre(MAN_ARGS); +static int man_SB_pre(MAN_ARGS); +static int man_SH_pre(MAN_ARGS); +static int man_SM_pre(MAN_ARGS); +static int man_SS_pre(MAN_ARGS); + +static const struct htmlman mans[MAN_MAX] = { + { man_br_pre, NULL }, /* br */ + { NULL, NULL }, /* TH */ + { man_SH_pre, NULL }, /* SH */ + { man_SS_pre, NULL }, /* SS */ + { man_IP_pre, NULL }, /* TP */ + { man_PP_pre, NULL }, /* LP */ + { man_PP_pre, NULL }, /* PP */ + { man_PP_pre, NULL }, /* P */ + { man_IP_pre, NULL }, /* IP */ + { man_HP_pre, NULL }, /* HP */ + { man_SM_pre, NULL }, /* SM */ + { man_SB_pre, NULL }, /* SB */ + { man_alt_pre, NULL }, /* BI */ + { man_alt_pre, NULL }, /* IB */ + { man_alt_pre, NULL }, /* BR */ + { man_alt_pre, NULL }, /* RB */ + { NULL, NULL }, /* R */ + { man_B_pre, NULL }, /* B */ + { man_I_pre, NULL }, /* I */ + { man_alt_pre, NULL }, /* IR */ + { man_alt_pre, NULL }, /* RI */ + { NULL, NULL }, /* na */ + { NULL, NULL }, /* i */ + { man_br_pre, NULL }, /* sp */ + { NULL, NULL }, /* nf */ + { NULL, NULL }, /* fi */ + { NULL, NULL }, /* r */ + { NULL, NULL }, /* RE */ + { man_RS_pre, NULL }, /* RS */ + { man_ign_pre, NULL }, /* DT */ + { man_ign_pre, NULL }, /* UC */ +}; + + +void +html_man(void *arg, const struct man *m) +{ + struct html *h; + struct tag *t; + + h = (struct html *)arg; + + print_gen_doctype(h); + + t = print_otag(h, TAG_HTML, 0, NULL); + print_man(man_meta(m), man_node(m), h); + print_tagq(h, t); + + printf("\n"); +} + + +static void +print_man(MAN_ARGS) +{ + struct tag *t; + struct htmlpair tag; + + t = print_otag(h, TAG_HEAD, 0, NULL); + + print_man_head(m, n, h); + print_tagq(h, t); + t = print_otag(h, TAG_BODY, 0, NULL); + + tag.key = ATTR_CLASS; + tag.val = "body"; + print_otag(h, TAG_DIV, 1, &tag); + + print_man_nodelist(m, n, h); + + print_tagq(h, t); +} + + +/* ARGSUSED */ +static void +print_man_head(MAN_ARGS) +{ + + print_gen_head(h); + bufinit(h); + buffmt(h, "%s(%d)", m->title, m->msec); + + print_otag(h, TAG_TITLE, 0, NULL); + print_text(h, h->buf); +} + + +static void +print_man_nodelist(MAN_ARGS) +{ + + print_man_node(m, n, h); + if (n->next) + print_man_nodelist(m, n->next, h); +} + + +static void +print_man_node(MAN_ARGS) +{ + int child; + struct tag *t; + + child = 1; + t = SLIST_FIRST(&h->tags); + + bufinit(h); + + switch (n->type) { + case (MAN_ROOT): + child = man_root_pre(m, n, h); + break; + case (MAN_TEXT): + print_text(h, n->string); + break; + default: + if (mans[n->tok].pre) + child = (*mans[n->tok].pre)(m, n, h); + break; + } + + if (child && n->child) + print_man_nodelist(m, n->child, h); + + print_stagq(h, t); + + bufinit(h); + + switch (n->type) { + case (MAN_ROOT): + man_root_post(m, n, h); + break; + case (MAN_TEXT): + break; + default: + if (mans[n->tok].post) + (*mans[n->tok].post)(m, n, h); + break; + } +} + + +static int +a2width(const struct man_node *n, struct roffsu *su) +{ + + if (MAN_TEXT != n->type) + return(0); + if (a2roffsu(n->string, su, SCALE_BU)) + return(1); + + return(0); +} + + +/* ARGSUSED */ +static int +man_root_pre(MAN_ARGS) +{ + struct htmlpair tag[2]; + struct tag *t, *tt; + char b[BUFSIZ], title[BUFSIZ]; + + b[0] = 0; + if (m->vol) + (void)strlcat(b, m->vol, BUFSIZ); + + (void)snprintf(title, BUFSIZ - 1, + "%s(%d)", m->title, m->msec); + + PAIR_CLASS_INIT(&tag[0], "header"); + bufcat_style(h, "width", "100%"); + PAIR_STYLE_INIT(&tag[1], h); + t = print_otag(h, TAG_TABLE, 2, tag); + tt = print_otag(h, TAG_TR, 0, NULL); + + bufinit(h); + bufcat_style(h, "width", "10%"); + PAIR_STYLE_INIT(&tag[0], h); + print_otag(h, TAG_TD, 1, tag); + print_text(h, title); + print_stagq(h, tt); + + bufinit(h); + bufcat_style(h, "width", "80%"); + bufcat_style(h, "white-space", "nowrap"); + bufcat_style(h, "text-align", "center"); + PAIR_STYLE_INIT(&tag[0], h); + print_otag(h, TAG_TD, 1, tag); + print_text(h, b); + print_stagq(h, tt); + + bufinit(h); + bufcat_style(h, "width", "10%"); + bufcat_style(h, "text-align", "right"); + PAIR_STYLE_INIT(&tag[0], h); + print_otag(h, TAG_TD, 1, tag); + print_text(h, title); + print_tagq(h, t); + return(1); +} + + +/* ARGSUSED */ +static void +man_root_post(MAN_ARGS) +{ + struct tm tm; + struct htmlpair tag[2]; + struct tag *t, *tt; + char b[BUFSIZ]; + + (void)localtime_r(&m->date, &tm); + + if (0 == strftime(b, BUFSIZ - 1, "%B %e, %Y", &tm)) + err(EXIT_FAILURE, "strftime"); + + PAIR_CLASS_INIT(&tag[0], "footer"); + bufcat_style(h, "width", "100%"); + PAIR_STYLE_INIT(&tag[1], h); + t = print_otag(h, TAG_TABLE, 2, tag); + tt = print_otag(h, TAG_TR, 0, NULL); + + bufinit(h); + bufcat_style(h, "width", "50%"); + PAIR_STYLE_INIT(&tag[0], h); + print_otag(h, TAG_TD, 1, tag); + print_text(h, b); + print_stagq(h, tt); + + bufinit(h); + bufcat_style(h, "width", "50%"); + bufcat_style(h, "text-align", "right"); + PAIR_STYLE_INIT(&tag[0], h); + print_otag(h, TAG_TD, 1, tag); + if (m->source) + print_text(h, m->source); + print_tagq(h, t); +} + + + +/* ARGSUSED */ +static int +man_br_pre(MAN_ARGS) +{ + struct roffsu su; + struct htmlpair tag; + + SCALE_VS_INIT(&su, 1); + + if (MAN_sp == n->tok && n->child) + a2roffsu(n->child->string, &su, SCALE_VS); + else if (MAN_br == n->tok) + su.scale = 0; + + bufcat_su(h, "height", &su); + PAIR_STYLE_INIT(&tag, h); + print_otag(h, TAG_DIV, 1, &tag); + return(0); +} + + +/* ARGSUSED */ +static int +man_SH_pre(MAN_ARGS) +{ + struct htmlpair tag[2]; + struct roffsu su; + + if (MAN_BODY == n->type) { + SCALE_HS_INIT(&su, INDENT); + bufcat_su(h, "margin-left", &su); + PAIR_CLASS_INIT(&tag[0], "sec-body"); + PAIR_STYLE_INIT(&tag[1], h); + print_otag(h, TAG_DIV, 2, tag); + return(1); + } else if (MAN_BLOCK == n->type) { + PAIR_CLASS_INIT(&tag[0], "sec-block"); + if (n->prev && MAN_SH == n->prev->tok) + if (NULL == n->prev->body->child) { + print_otag(h, TAG_DIV, 1, tag); + return(1); + } + + SCALE_VS_INIT(&su, 1); + bufcat_su(h, "margin-top", &su); + if (NULL == n->next) + bufcat_su(h, "margin-bottom", &su); + PAIR_STYLE_INIT(&tag[1], h); + print_otag(h, TAG_DIV, 2, tag); + return(1); + } + + PAIR_CLASS_INIT(&tag[0], "sec-head"); + print_otag(h, TAG_DIV, 1, tag); + return(1); +} + + +/* ARGSUSED */ +static int +man_alt_pre(MAN_ARGS) +{ + const struct man_node *nn; + struct tag *t; + int i; + struct htmlpair tagi, tagb, *tagp; + + PAIR_CLASS_INIT(&tagi, "italic"); + PAIR_CLASS_INIT(&tagb, "bold"); + + for (i = 0, nn = n->child; nn; nn = nn->next, i++) { + switch (n->tok) { + case (MAN_BI): + tagp = i % 2 ? &tagi : &tagb; + break; + case (MAN_IB): + tagp = i % 2 ? &tagb : &tagi; + break; + case (MAN_RI): + tagp = i % 2 ? &tagi : NULL; + break; + case (MAN_IR): + tagp = i % 2 ? NULL : &tagi; + break; + case (MAN_BR): + tagp = i % 2 ? NULL : &tagb; + break; + case (MAN_RB): + tagp = i % 2 ? &tagb : NULL; + break; + default: + abort(); + /* NOTREACHED */ + } + + if (i) + h->flags |= HTML_NOSPACE; + + if (tagp) { + t = print_otag(h, TAG_SPAN, 1, tagp); + print_man_node(m, nn, h); + print_tagq(h, t); + } else + print_man_node(m, nn, h); + } + + return(0); +} + + +/* ARGSUSED */ +static int +man_SB_pre(MAN_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "small bold"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +man_SM_pre(MAN_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "small"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +man_SS_pre(MAN_ARGS) +{ + struct htmlpair tag[3]; + struct roffsu su; + + SCALE_VS_INIT(&su, 1); + + if (MAN_BODY == n->type) { + PAIR_CLASS_INIT(&tag[0], "ssec-body"); + if (n->parent->next && n->child) { + bufcat_su(h, "margin-bottom", &su); + PAIR_STYLE_INIT(&tag[1], h); + print_otag(h, TAG_DIV, 2, tag); + return(1); + } + + print_otag(h, TAG_DIV, 1, tag); + return(1); + } else if (MAN_BLOCK == n->type) { + PAIR_CLASS_INIT(&tag[0], "ssec-block"); + if (n->prev && MAN_SS == n->prev->tok) + if (n->prev->body->child) { + bufcat_su(h, "margin-top", &su); + PAIR_STYLE_INIT(&tag[1], h); + print_otag(h, TAG_DIV, 2, tag); + return(1); + } + + print_otag(h, TAG_DIV, 1, tag); + return(1); + } + + SCALE_HS_INIT(&su, INDENT - HALFINDENT); + bufcat_su(h, "margin-left", &su); + PAIR_CLASS_INIT(&tag[0], "ssec-head"); + PAIR_STYLE_INIT(&tag[1], h); + print_otag(h, TAG_DIV, 2, tag); + return(1); +} + + +/* ARGSUSED */ +static int +man_PP_pre(MAN_ARGS) +{ + struct htmlpair tag; + struct roffsu su; + int i; + + if (MAN_BLOCK != n->type) + return(1); + + i = 0; + + if (MAN_ROOT == n->parent->tok) { + SCALE_HS_INIT(&su, INDENT); + bufcat_su(h, "margin-left", &su); + i++; + } + if (n->next && n->next->child) { + SCALE_VS_INIT(&su, 1); + bufcat_su(h, "margin-bottom", &su); + i++; + } + + PAIR_STYLE_INIT(&tag, h); + print_otag(h, TAG_DIV, i ? 1 : 0, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +man_IP_pre(MAN_ARGS) +{ + struct roffsu su; + struct htmlpair tag; + const struct man_node *nn; + int width; + + /* + * This scattering of 1-BU margins and pads is to make sure that + * when text overruns its box, the subsequent text isn't flush + * up against it. However, the rest of the right-hand box must + * also be adjusted in consideration of this 1-BU space. + */ + + if (MAN_BODY == n->type) { + SCALE_HS_INIT(&su, INDENT); + bufcat_su(h, "margin-left", &su); + PAIR_STYLE_INIT(&tag, h); + print_otag(h, TAG_DIV, 1, &tag); + return(1); + } + + nn = MAN_BLOCK == n->type ? + n->head->child : n->parent->head->child; + + SCALE_HS_INIT(&su, INDENT); + width = 0; + + if (MAN_IP == n->tok && NULL != nn) + if (NULL != (nn = nn->next)) { + for ( ; nn->next; nn = nn->next) + /* Do nothing. */ ; + width = a2width(nn, &su); + } + + if (MAN_TP == n->tok && NULL != nn) + width = a2width(nn, &su); + + if (MAN_BLOCK == n->type) { + bufcat_su(h, "margin-left", &su); + SCALE_VS_INIT(&su, 1); + bufcat_su(h, "margin-top", &su); + bufcat_style(h, "clear", "both"); + PAIR_STYLE_INIT(&tag, h); + print_otag(h, TAG_DIV, 1, &tag); + return(1); + } + + bufcat_su(h, "min-width", &su); + SCALE_INVERT(&su); + bufcat_su(h, "margin-left", &su); + SCALE_HS_INIT(&su, 1); + bufcat_su(h, "margin-right", &su); + bufcat_style(h, "clear", "left"); + + if (n->next && n->next->child) + bufcat_style(h, "float", "left"); + + PAIR_STYLE_INIT(&tag, h); + print_otag(h, TAG_DIV, 1, &tag); + + /* With a length string, manually omit the last child. */ + + if ( ! width) + return(1); + + if (MAN_IP == n->tok) + for (nn = n->child; nn->next; nn = nn->next) + print_man_node(m, nn, h); + if (MAN_TP == n->tok) + for (nn = n->child->next; nn; nn = nn->next) + print_man_node(m, nn, h); + + return(0); +} + + +/* ARGSUSED */ +static int +man_HP_pre(MAN_ARGS) +{ + const struct man_node *nn; + struct htmlpair tag; + struct roffsu su; + + if (MAN_HEAD == n->type) + return(0); + + nn = MAN_BLOCK == n->type ? + n->head->child : n->parent->head->child; + + SCALE_HS_INIT(&su, INDENT); + + if (NULL != nn) + (void)a2width(nn, &su); + + if (MAN_BLOCK == n->type) { + bufcat_su(h, "margin-left", &su); + SCALE_VS_INIT(&su, 1); + bufcat_su(h, "margin-top", &su); + bufcat_style(h, "clear", "both"); + PAIR_STYLE_INIT(&tag, h); + print_otag(h, TAG_DIV, 1, &tag); + return(1); + } + + bufcat_su(h, "margin-left", &su); + SCALE_INVERT(&su); + bufcat_su(h, "text-indent", &su); + + PAIR_STYLE_INIT(&tag, h); + print_otag(h, TAG_DIV, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +man_B_pre(MAN_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "bold"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +man_I_pre(MAN_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "italic"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +man_ign_pre(MAN_ARGS) +{ + + return(0); +} + + +/* ARGSUSED */ +static int +man_RS_pre(MAN_ARGS) +{ + struct htmlpair tag; + struct roffsu su; + + if (MAN_HEAD == n->type) + return(0); + else if (MAN_BODY == n->type) + return(1); + + SCALE_HS_INIT(&su, INDENT); + bufcat_su(h, "margin-left", &su); + + if (n->head->child) { + SCALE_VS_INIT(&su, 1); + a2width(n->head->child, &su); + bufcat_su(h, "margin-top", &su); + } + + PAIR_STYLE_INIT(&tag, h); + print_otag(h, TAG_DIV, 1, &tag); + return(1); +} diff --git a/usr.bin/mandoc/man_macro.c b/usr.bin/mandoc/man_macro.c new file mode 100644 index 0000000000..6a62e78fb5 --- /dev/null +++ b/usr.bin/mandoc/man_macro.c @@ -0,0 +1,374 @@ +/* $Id: man_macro.c,v 1.8 2009/09/18 22:46:14 schwarze Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include +#include +#include + +#include "libman.h" + +#define REW_REWIND (0) /* See rew_scope(). */ +#define REW_NOHALT (1) /* See rew_scope(). */ +#define REW_HALT (2) /* See rew_scope(). */ + +static int in_line_eoln(MACRO_PROT_ARGS); +static int blk_imp(MACRO_PROT_ARGS); +static int blk_close(MACRO_PROT_ARGS); + +static int rew_scope(enum man_type, struct man *, int); +static int rew_dohalt(int, enum man_type, + const struct man_node *); +static int rew_block(int, enum man_type, + const struct man_node *); + +const struct man_macro __man_macros[MAN_MAX] = { + { in_line_eoln, 0 }, /* br */ + { in_line_eoln, 0 }, /* TH */ + { blk_imp, MAN_SCOPED }, /* SH */ + { blk_imp, MAN_SCOPED }, /* SS */ + { blk_imp, MAN_SCOPED | MAN_FSCOPED }, /* TP */ + { blk_imp, 0 }, /* LP */ + { blk_imp, 0 }, /* PP */ + { blk_imp, 0 }, /* P */ + { blk_imp, 0 }, /* IP */ + { blk_imp, 0 }, /* HP */ + { in_line_eoln, MAN_SCOPED }, /* SM */ + { in_line_eoln, MAN_SCOPED }, /* SB */ + { in_line_eoln, 0 }, /* BI */ + { in_line_eoln, 0 }, /* IB */ + { in_line_eoln, 0 }, /* BR */ + { in_line_eoln, 0 }, /* RB */ + { in_line_eoln, MAN_SCOPED }, /* R */ + { in_line_eoln, MAN_SCOPED }, /* B */ + { in_line_eoln, MAN_SCOPED }, /* I */ + { in_line_eoln, 0 }, /* IR */ + { in_line_eoln, 0 }, /* RI */ + { in_line_eoln, 0 }, /* na */ + { in_line_eoln, 0 }, /* i */ + { in_line_eoln, 0 }, /* sp */ + { in_line_eoln, 0 }, /* nf */ + { in_line_eoln, 0 }, /* fi */ + { in_line_eoln, 0 }, /* r */ + { blk_close, 0 }, /* RE */ + { blk_imp, MAN_EXPLICIT }, /* RS */ + { in_line_eoln, 0 }, /* DT */ + { in_line_eoln, 0 }, /* UC */ +}; + +const struct man_macro * const man_macros = __man_macros; + + +int +man_unscope(struct man *m, const struct man_node *n) +{ + + assert(n); + m->next = MAN_NEXT_SIBLING; + + /* LINTED */ + while (m->last != n) { + if ( ! man_valid_post(m)) + return(0); + if ( ! man_action_post(m)) + return(0); + m->last = m->last->parent; + assert(m->last); + } + + if ( ! man_valid_post(m)) + return(0); + return(man_action_post(m)); +} + + +static int +rew_block(int ntok, enum man_type type, const struct man_node *n) +{ + + if (MAN_BLOCK == type && ntok == n->parent->tok && + MAN_BODY == n->parent->type) + return(REW_REWIND); + return(ntok == n->tok ? REW_HALT : REW_NOHALT); +} + + +/* + * There are three scope levels: scoped to the root (all), scoped to the + * section (all less sections), and scoped to subsections (all less + * sections and subsections). + */ +static int +rew_dohalt(int tok, enum man_type type, const struct man_node *n) +{ + int c; + + if (MAN_ROOT == n->type) + return(REW_HALT); + assert(n->parent); + if (MAN_ROOT == n->parent->type) + return(REW_REWIND); + if (MAN_VALID & n->flags) + return(REW_NOHALT); + + /* Rewind to ourselves, first. */ + if (type == n->type && tok == n->tok) + return(REW_REWIND); + + switch (tok) { + case (MAN_SH): + break; + case (MAN_SS): + /* Rewind to a section, if a block. */ + if (REW_NOHALT != (c = rew_block(MAN_SH, type, n))) + return(c); + break; + case (MAN_RS): + /* Rewind to a subsection, if a block. */ + if (REW_NOHALT != (c = rew_block(MAN_SS, type, n))) + return(c); + /* Rewind to a section, if a block. */ + if (REW_NOHALT != (c = rew_block(MAN_SH, type, n))) + return(c); + break; + default: + /* Rewind to an offsetter, if a block. */ + if (REW_NOHALT != (c = rew_block(MAN_RS, type, n))) + return(c); + /* Rewind to a subsection, if a block. */ + if (REW_NOHALT != (c = rew_block(MAN_SS, type, n))) + return(c); + /* Rewind to a section, if a block. */ + if (REW_NOHALT != (c = rew_block(MAN_SH, type, n))) + return(c); + break; + } + + return(REW_NOHALT); +} + + +/* + * Rewinding entails ascending the parse tree until a coherent point, + * for example, the `SH' macro will close out any intervening `SS' + * scopes. When a scope is closed, it must be validated and actioned. + */ +static int +rew_scope(enum man_type type, struct man *m, int tok) +{ + struct man_node *n; + int c; + + /* LINTED */ + for (n = m->last; n; n = n->parent) { + /* + * Whether we should stop immediately (REW_HALT), stop + * and rewind until this point (REW_REWIND), or keep + * rewinding (REW_NOHALT). + */ + c = rew_dohalt(tok, type, n); + if (REW_HALT == c) + return(1); + if (REW_REWIND == c) + break; + } + + /* Rewind until the current point. */ + + assert(n); + return(man_unscope(m, n)); +} + + +/* ARGSUSED */ +int +blk_close(MACRO_PROT_ARGS) +{ + int ntok; + const struct man_node *nn; + + switch (tok) { + case (MAN_RE): + ntok = MAN_RS; + break; + default: + abort(); + /* NOTREACHED */ + } + + for (nn = m->last->parent; nn; nn = nn->parent) + if (ntok == nn->tok) + break; + + if (NULL == nn) + if ( ! man_pwarn(m, line, ppos, WNOSCOPE)) + return(0); + + if ( ! rew_scope(MAN_BODY, m, ntok)) + return(0); + if ( ! rew_scope(MAN_BLOCK, m, ntok)) + return(0); + m->next = MAN_NEXT_SIBLING; + return(1); +} + + +/* + * Parse an implicit-block macro. These contain a MAN_HEAD and a + * MAN_BODY contained within a MAN_BLOCK. Rules for closing out other + * scopes, such as `SH' closing out an `SS', are defined in the rew + * routines. + */ +int +blk_imp(MACRO_PROT_ARGS) +{ + int w, la; + char *p; + struct man_node *n; + + /* Close out prior scopes. */ + + if ( ! rew_scope(MAN_BODY, m, tok)) + return(0); + if ( ! rew_scope(MAN_BLOCK, m, tok)) + return(0); + + /* Allocate new block & head scope. */ + + if ( ! man_block_alloc(m, line, ppos, tok)) + return(0); + if ( ! man_head_alloc(m, line, ppos, tok)) + return(0); + + n = m->last; + + /* Add line arguments. */ + + for (;;) { + la = *pos; + w = man_args(m, line, pos, buf, &p); + + if (-1 == w) + return(0); + if (0 == w) + break; + + if ( ! man_word_alloc(m, line, la, p)) + return(0); + } + + /* Close out head and open body (unless MAN_SCOPE). */ + + if (MAN_SCOPED & man_macros[tok].flags) { + /* If we're forcing scope (`TP'), keep it open. */ + if (MAN_FSCOPED & man_macros[tok].flags) { + m->flags |= MAN_BLINE; + return(1); + } else if (n == m->last) { + m->flags |= MAN_BLINE; + return(1); + } + } + + if ( ! rew_scope(MAN_HEAD, m, tok)) + return(0); + + return(man_body_alloc(m, line, ppos, tok)); +} + + +int +in_line_eoln(MACRO_PROT_ARGS) +{ + int w, la; + char *p; + struct man_node *n; + + if ( ! man_elem_alloc(m, line, ppos, tok)) + return(0); + + n = m->last; + + for (;;) { + la = *pos; + w = man_args(m, line, pos, buf, &p); + + if (-1 == w) + return(0); + if (0 == w) + break; + + if ( ! man_word_alloc(m, line, la, p)) + return(0); + } + + if (n == m->last && MAN_SCOPED & man_macros[tok].flags) { + m->flags |= MAN_ELINE; + return(1); + } + + /* + * Note that when TH is pruned, we'll be back at the root, so + * make sure that we don't clobber as its sibling. + */ + + for ( ; m->last; m->last = m->last->parent) { + if (m->last == n) + break; + if (m->last->type == MAN_ROOT) + break; + if ( ! man_valid_post(m)) + return(0); + if ( ! man_action_post(m)) + return(0); + } + + assert(m->last); + + /* + * Same here regarding whether we're back at the root. + */ + + if (m->last->type != MAN_ROOT && ! man_valid_post(m)) + return(0); + if (m->last->type != MAN_ROOT && ! man_action_post(m)) + return(0); + if (m->last->type != MAN_ROOT) + m->next = MAN_NEXT_SIBLING; + + return(1); +} + + +int +man_macroend(struct man *m) +{ + struct man_node *n; + + n = MAN_VALID & m->last->flags ? + m->last->parent : m->last; + + for ( ; n; n = n->parent) { + if (MAN_BLOCK != n->type) + continue; + if ( ! (MAN_EXPLICIT & man_macros[n->tok].flags)) + continue; + if ( ! man_nwarn(m, n, WEXITSCOPE)) + return(0); + } + + return(man_unscope(m, m->first)); +} diff --git a/usr.bin/mandoc/man_term.c b/usr.bin/mandoc/man_term.c new file mode 100644 index 0000000000..f6d59cd5b4 --- /dev/null +++ b/usr.bin/mandoc/man_term.c @@ -0,0 +1,986 @@ +/* $Id: man_term.c,v 1.18 2009/10/21 19:13:50 schwarze Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include + +#include +#include +#include +#include +#include +#include + +#include "out.h" +#include "man.h" +#include "term.h" +#include "chars.h" +#include "main.h" + +#define INDENT 7 +#define HALFINDENT 3 + +struct mtermp { + int fl; +#define MANT_LITERAL (1 << 0) + /* + * Default amount to indent the left margin after leading text + * has been printed (e.g., `HP' left-indent, `TP' and `IP' body + * indent). This needs to be saved because `HP' and so on, if + * not having a specified value, must default. + * + * Note that this is the indentation AFTER the left offset, so + * the total offset is usually offset + lmargin. + */ + size_t lmargin; + /* + * The default offset, i.e., the amount between any text and the + * page boundary. + */ + size_t offset; +}; + +#define DECL_ARGS struct termp *p, \ + struct mtermp *mt, \ + const struct man_node *n, \ + const struct man_meta *m + +struct termact { + int (*pre)(DECL_ARGS); + void (*post)(DECL_ARGS); +}; + +static int arg2width(const struct man_node *); +static int arg2height(const struct man_node *); + +static void print_head(struct termp *, + const struct man_meta *); +static void print_body(DECL_ARGS); +static void print_node(DECL_ARGS); +static void print_foot(struct termp *, + const struct man_meta *); +static void print_bvspace(struct termp *, + const struct man_node *); + +static int pre_B(DECL_ARGS); +static int pre_BI(DECL_ARGS); +static int pre_HP(DECL_ARGS); +static int pre_I(DECL_ARGS); +static int pre_IP(DECL_ARGS); +static int pre_IR(DECL_ARGS); +static int pre_PP(DECL_ARGS); +static int pre_RB(DECL_ARGS); +static int pre_RI(DECL_ARGS); +static int pre_RS(DECL_ARGS); +static int pre_SH(DECL_ARGS); +static int pre_SS(DECL_ARGS); +static int pre_TP(DECL_ARGS); +static int pre_br(DECL_ARGS); +static int pre_fi(DECL_ARGS); +static int pre_ign(DECL_ARGS); +static int pre_nf(DECL_ARGS); +static int pre_r(DECL_ARGS); +static int pre_sp(DECL_ARGS); + +static void post_B(DECL_ARGS); +static void post_I(DECL_ARGS); +static void post_IP(DECL_ARGS); +static void post_HP(DECL_ARGS); +static void post_RS(DECL_ARGS); +static void post_SH(DECL_ARGS); +static void post_SS(DECL_ARGS); +static void post_TP(DECL_ARGS); +static void post_i(DECL_ARGS); + +static const struct termact termacts[MAN_MAX] = { + { pre_br, NULL }, /* br */ + { NULL, NULL }, /* TH */ + { pre_SH, post_SH }, /* SH */ + { pre_SS, post_SS }, /* SS */ + { pre_TP, post_TP }, /* TP */ + { pre_PP, NULL }, /* LP */ + { pre_PP, NULL }, /* PP */ + { pre_PP, NULL }, /* P */ + { pre_IP, post_IP }, /* IP */ + { pre_HP, post_HP }, /* HP */ + { NULL, NULL }, /* SM */ + { pre_B, post_B }, /* SB */ + { pre_BI, NULL }, /* BI */ + { pre_BI, NULL }, /* IB */ + { pre_RB, NULL }, /* BR */ + { pre_RB, NULL }, /* RB */ + { NULL, NULL }, /* R */ + { pre_B, post_B }, /* B */ + { pre_I, post_I }, /* I */ + { pre_IR, NULL }, /* IR */ + { pre_RI, NULL }, /* RI */ + { NULL, NULL }, /* na */ + { pre_I, post_i }, /* i */ + { pre_sp, NULL }, /* sp */ + { pre_nf, NULL }, /* nf */ + { pre_fi, NULL }, /* fi */ + { pre_r, NULL }, /* r */ + { NULL, NULL }, /* RE */ + { pre_RS, post_RS }, /* RS */ + { pre_ign, NULL }, /* DT */ + { pre_ign, NULL }, /* UC */ +}; + + + +void +terminal_man(void *arg, const struct man *man) +{ + struct termp *p; + const struct man_node *n; + const struct man_meta *m; + struct mtermp mt; + + p = (struct termp *)arg; + + if (NULL == p->symtab) + switch (p->enc) { + case (TERMENC_ASCII): + p->symtab = chars_init(CHARS_ASCII); + break; + default: + abort(); + /* NOTREACHED */ + } + + n = man_node(man); + m = man_meta(man); + + print_head(p, m); + p->flags |= TERMP_NOSPACE; + + mt.fl = 0; + mt.lmargin = INDENT; + mt.offset = INDENT; + + if (n->child) + print_body(p, &mt, n->child, m); + print_foot(p, m); +} + + +static int +arg2height(const struct man_node *n) +{ + struct roffsu su; + + assert(MAN_TEXT == n->type); + assert(n->string); + if ( ! a2roffsu(n->string, &su, SCALE_VS)) + SCALE_VS_INIT(&su, strlen(n->string)); + + return((int)term_vspan(&su)); +} + + +static int +arg2width(const struct man_node *n) +{ + struct roffsu su; + + assert(MAN_TEXT == n->type); + assert(n->string); + if ( ! a2roffsu(n->string, &su, SCALE_BU)) + return(-1); + + return((int)term_hspan(&su)); +} + + +static void +print_bvspace(struct termp *p, const struct man_node *n) +{ + term_newln(p); + + if (NULL == n->prev) + return; + + if (MAN_SS == n->prev->tok) + return; + if (MAN_SH == n->prev->tok) + return; + + term_vspace(p); +} + + +/* ARGSUSED */ +static int +pre_ign(DECL_ARGS) +{ + + return(0); +} + + +/* ARGSUSED */ +static int +pre_I(DECL_ARGS) +{ + + p->under++; + return(1); +} + + +/* ARGSUSED */ +static int +pre_r(DECL_ARGS) +{ + + p->bold = p->under = 0; + return(1); +} + + +/* ARGSUSED */ +static void +post_i(DECL_ARGS) +{ + + if (n->nchild) + p->under--; +} + + +/* ARGSUSED */ +static void +post_I(DECL_ARGS) +{ + + p->under--; +} + + +/* ARGSUSED */ +static int +pre_fi(DECL_ARGS) +{ + + mt->fl &= ~MANT_LITERAL; + return(1); +} + + +/* ARGSUSED */ +static int +pre_nf(DECL_ARGS) +{ + + term_newln(p); + mt->fl |= MANT_LITERAL; + return(1); +} + + +/* ARGSUSED */ +static int +pre_IR(DECL_ARGS) +{ + const struct man_node *nn; + int i; + + for (i = 0, nn = n->child; nn; nn = nn->next, i++) { + if ( ! (i % 2)) + p->under++; + if (i > 0) + p->flags |= TERMP_NOSPACE; + print_node(p, mt, nn, m); + if ( ! (i % 2)) + p->under--; + } + return(0); +} + + +/* ARGSUSED */ +static int +pre_RB(DECL_ARGS) +{ + const struct man_node *nn; + int i; + + for (i = 0, nn = n->child; nn; nn = nn->next, i++) { + if (i % 2 && MAN_RB == n->tok) + p->bold++; + else if ( ! (i % 2) && MAN_RB != n->tok) + p->bold++; + + if (i > 0) + p->flags |= TERMP_NOSPACE; + + print_node(p, mt, nn, m); + + if (i % 2 && MAN_RB == n->tok) + p->bold--; + else if ( ! (i % 2) && MAN_RB != n->tok) + p->bold--; + } + return(0); +} + + +/* ARGSUSED */ +static int +pre_RI(DECL_ARGS) +{ + const struct man_node *nn; + int i; + + for (i = 0, nn = n->child; nn; nn = nn->next, i++) { + if ( ! (i % 2)) + p->under++; + if (i > 0) + p->flags |= TERMP_NOSPACE; + print_node(p, mt, nn, m); + if ( ! (i % 2)) + p->under--; + } + return(0); +} + + +/* ARGSUSED */ +static int +pre_BI(DECL_ARGS) +{ + const struct man_node *nn; + int i; + + for (i = 0, nn = n->child; nn; nn = nn->next, i++) { + if (i % 2 && MAN_BI == n->tok) + p->under++; + else if (i % 2) + p->bold++; + else if (MAN_BI == n->tok) + p->bold++; + else + p->under++; + + if (i) + p->flags |= TERMP_NOSPACE; + print_node(p, mt, nn, m); + + if (i % 2 && MAN_BI == n->tok) + p->under--; + else if (i % 2) + p->bold--; + else if (MAN_BI == n->tok) + p->bold--; + else + p->under--; + } + return(0); +} + + +/* ARGSUSED */ +static int +pre_B(DECL_ARGS) +{ + + p->bold++; + return(1); +} + + +/* ARGSUSED */ +static void +post_B(DECL_ARGS) +{ + + p->bold--; +} + + +/* ARGSUSED */ +static int +pre_sp(DECL_ARGS) +{ + int i, len; + + len = n->child ? arg2height(n->child) : 1; + + if (0 == len) + term_newln(p); + for (i = 0; i < len; i++) + term_vspace(p); + + return(0); +} + + +/* ARGSUSED */ +static int +pre_br(DECL_ARGS) +{ + + term_newln(p); + return(0); +} + + +/* ARGSUSED */ +static int +pre_HP(DECL_ARGS) +{ + size_t len; + int ival; + const struct man_node *nn; + + switch (n->type) { + case (MAN_BLOCK): + print_bvspace(p, n); + return(1); + case (MAN_BODY): + p->flags |= TERMP_NOBREAK; + p->flags |= TERMP_TWOSPACE; + break; + default: + return(0); + } + + len = mt->lmargin; + ival = -1; + + /* Calculate offset. */ + + if (NULL != (nn = n->parent->head->child)) + if ((ival = arg2width(nn)) >= 0) + len = (size_t)ival; + + if (0 == len) + len = 1; + + p->offset = mt->offset; + p->rmargin = mt->offset + len; + + if (ival >= 0) + mt->lmargin = (size_t)ival; + + return(1); +} + + +/* ARGSUSED */ +static void +post_HP(DECL_ARGS) +{ + + switch (n->type) { + case (MAN_BLOCK): + term_flushln(p); + break; + case (MAN_BODY): + term_flushln(p); + p->flags &= ~TERMP_NOBREAK; + p->flags &= ~TERMP_TWOSPACE; + p->offset = mt->offset; + p->rmargin = p->maxrmargin; + break; + default: + break; + } +} + + +/* ARGSUSED */ +static int +pre_PP(DECL_ARGS) +{ + + switch (n->type) { + case (MAN_BLOCK): + mt->lmargin = INDENT; + print_bvspace(p, n); + break; + default: + p->offset = mt->offset; + break; + } + + return(1); +} + + +/* ARGSUSED */ +static int +pre_IP(DECL_ARGS) +{ + const struct man_node *nn; + size_t len; + int ival; + + switch (n->type) { + case (MAN_BODY): + p->flags |= TERMP_NOLPAD; + p->flags |= TERMP_NOSPACE; + break; + case (MAN_HEAD): + p->flags |= TERMP_NOBREAK; + p->flags |= TERMP_TWOSPACE; + break; + case (MAN_BLOCK): + print_bvspace(p, n); + /* FALLTHROUGH */ + default: + return(1); + } + + len = mt->lmargin; + ival = -1; + + /* Calculate offset. */ + + if (NULL != (nn = n->parent->head->child)) + if (NULL != (nn = nn->next)) { + for ( ; nn->next; nn = nn->next) + /* Do nothing. */ ; + if ((ival = arg2width(nn)) >= 0) + len = (size_t)ival; + } + + switch (n->type) { + case (MAN_HEAD): + /* Handle zero-width lengths. */ + if (0 == len) + len = 1; + + p->offset = mt->offset; + p->rmargin = mt->offset + len; + if (ival < 0) + break; + + /* Set the saved left-margin. */ + mt->lmargin = (size_t)ival; + + /* Don't print the length value. */ + for (nn = n->child; nn->next; nn = nn->next) + print_node(p, mt, nn, m); + return(0); + case (MAN_BODY): + p->offset = mt->offset + len; + p->rmargin = p->maxrmargin; + break; + default: + break; + } + + return(1); +} + + +/* ARGSUSED */ +static void +post_IP(DECL_ARGS) +{ + + switch (n->type) { + case (MAN_HEAD): + term_flushln(p); + p->flags &= ~TERMP_NOBREAK; + p->flags &= ~TERMP_TWOSPACE; + p->rmargin = p->maxrmargin; + break; + case (MAN_BODY): + term_flushln(p); + p->flags &= ~TERMP_NOLPAD; + break; + default: + break; + } +} + + +/* ARGSUSED */ +static int +pre_TP(DECL_ARGS) +{ + const struct man_node *nn; + size_t len; + int ival; + + switch (n->type) { + case (MAN_HEAD): + p->flags |= TERMP_NOBREAK; + p->flags |= TERMP_TWOSPACE; + break; + case (MAN_BODY): + p->flags |= TERMP_NOLPAD; + p->flags |= TERMP_NOSPACE; + break; + case (MAN_BLOCK): + print_bvspace(p, n); + /* FALLTHROUGH */ + default: + return(1); + } + + len = (size_t)mt->lmargin; + ival = -1; + + /* Calculate offset. */ + + if (NULL != (nn = n->parent->head->child)) + if (NULL != nn->next) + if ((ival = arg2width(nn)) >= 0) + len = (size_t)ival; + + switch (n->type) { + case (MAN_HEAD): + /* Handle zero-length properly. */ + if (0 == len) + len = 1; + + p->offset = mt->offset; + p->rmargin = mt->offset + len; + + /* Don't print same-line elements. */ + for (nn = n->child; nn; nn = nn->next) + if (nn->line > n->line) + print_node(p, mt, nn, m); + + if (ival >= 0) + mt->lmargin = (size_t)ival; + + return(0); + case (MAN_BODY): + p->offset = mt->offset + len; + p->rmargin = p->maxrmargin; + break; + default: + break; + } + + return(1); +} + + +/* ARGSUSED */ +static void +post_TP(DECL_ARGS) +{ + + switch (n->type) { + case (MAN_HEAD): + term_flushln(p); + p->flags &= ~TERMP_NOBREAK; + p->flags &= ~TERMP_TWOSPACE; + p->rmargin = p->maxrmargin; + break; + case (MAN_BODY): + term_flushln(p); + p->flags &= ~TERMP_NOLPAD; + break; + default: + break; + } +} + + +/* ARGSUSED */ +static int +pre_SS(DECL_ARGS) +{ + + switch (n->type) { + case (MAN_BLOCK): + mt->lmargin = INDENT; + mt->offset = INDENT; + /* If following a prior empty `SS', no vspace. */ + if (n->prev && MAN_SS == n->prev->tok) + if (NULL == n->prev->body->child) + break; + if (NULL == n->prev) + break; + term_vspace(p); + break; + case (MAN_HEAD): + p->bold++; + p->offset = HALFINDENT; + break; + case (MAN_BODY): + p->offset = mt->offset; + break; + default: + break; + } + + return(1); +} + + +/* ARGSUSED */ +static void +post_SS(DECL_ARGS) +{ + + switch (n->type) { + case (MAN_HEAD): + term_newln(p); + p->bold--; + break; + case (MAN_BODY): + term_newln(p); + break; + default: + break; + } +} + + +/* ARGSUSED */ +static int +pre_SH(DECL_ARGS) +{ + + switch (n->type) { + case (MAN_BLOCK): + mt->lmargin = INDENT; + mt->offset = INDENT; + /* If following a prior empty `SH', no vspace. */ + if (n->prev && MAN_SH == n->prev->tok) + if (NULL == n->prev->body->child) + break; + term_vspace(p); + break; + case (MAN_HEAD): + p->bold++; + p->offset = 0; + break; + case (MAN_BODY): + p->offset = mt->offset; + break; + default: + break; + } + + return(1); +} + + +/* ARGSUSED */ +static void +post_SH(DECL_ARGS) +{ + + switch (n->type) { + case (MAN_HEAD): + term_newln(p); + p->bold--; + break; + case (MAN_BODY): + term_newln(p); + break; + default: + break; + } +} + + +/* ARGSUSED */ +static int +pre_RS(DECL_ARGS) +{ + const struct man_node *nn; + int ival; + + switch (n->type) { + case (MAN_BLOCK): + term_newln(p); + return(1); + case (MAN_HEAD): + return(0); + default: + break; + } + + if (NULL == (nn = n->parent->head->child)) { + mt->offset = mt->lmargin + INDENT; + p->offset = mt->offset; + return(1); + } + + if ((ival = arg2width(nn)) < 0) + return(1); + + mt->offset = INDENT + (size_t)ival; + p->offset = mt->offset; + + return(1); +} + + +/* ARGSUSED */ +static void +post_RS(DECL_ARGS) +{ + + switch (n->type) { + case (MAN_BLOCK): + mt->offset = mt->lmargin = INDENT; + break; + default: + term_newln(p); + p->offset = INDENT; + break; + } +} + + +static void +print_node(DECL_ARGS) +{ + int c, sz; + + c = 1; + + switch (n->type) { + case(MAN_TEXT): + if (0 == *n->string) { + term_vspace(p); + break; + } + /* + * Note! This is hacky. Here, we recognise the `\c' + * escape embedded in so many -man pages. It's supposed + * to remove the subsequent space, so we mark NOSPACE if + * it's encountered in the string. + */ + sz = (int)strlen(n->string); + term_word(p, n->string); + if (sz >= 2 && n->string[sz - 1] == 'c' && + n->string[sz - 2] == '\\') + p->flags |= TERMP_NOSPACE; + /* FIXME: this means that macro lines are munged! */ + if (MANT_LITERAL & mt->fl) { + p->flags |= TERMP_NOSPACE; + term_flushln(p); + } + break; + default: + if (termacts[n->tok].pre) + c = (*termacts[n->tok].pre)(p, mt, n, m); + break; + } + + if (c && n->child) + print_body(p, mt, n->child, m); + + if (MAN_TEXT != n->type) + if (termacts[n->tok].post) + (*termacts[n->tok].post)(p, mt, n, m); +} + + +static void +print_body(DECL_ARGS) +{ + + print_node(p, mt, n, m); + if ( ! n->next) + return; + print_body(p, mt, n->next, m); +} + + +static void +print_foot(struct termp *p, const struct man_meta *meta) +{ + struct tm *tm; + char buf[BUFSIZ]; + + tm = localtime(&meta->date); + + if (0 == strftime(buf, p->rmargin, "%B %d, %Y", tm)) + (void)strlcpy(buf, "(invalid date)", BUFSIZ); + + term_vspace(p); + + p->flags |= TERMP_NOSPACE | TERMP_NOBREAK; + p->rmargin = p->maxrmargin - strlen(buf); + p->offset = 0; + + if (meta->source) + term_word(p, meta->source); + if (meta->source) + term_word(p, ""); + term_flushln(p); + + p->flags |= TERMP_NOLPAD | TERMP_NOSPACE; + p->offset = p->rmargin; + p->rmargin = p->maxrmargin; + p->flags &= ~TERMP_NOBREAK; + + term_word(p, buf); + term_flushln(p); +} + + +static void +print_head(struct termp *p, const struct man_meta *meta) +{ + char *buf, *title; + + p->rmargin = p->maxrmargin; + p->offset = 0; + + if (NULL == (buf = malloc(p->rmargin))) + err(EXIT_FAILURE, "malloc"); + if (NULL == (title = malloc(p->rmargin))) + err(EXIT_FAILURE, "malloc"); + + if (meta->vol) + (void)strlcpy(buf, meta->vol, p->rmargin); + else + *buf = 0; + + (void)snprintf(title, p->rmargin, "%s(%d)", + meta->title, meta->msec); + + p->offset = 0; + p->rmargin = (p->maxrmargin - strlen(buf) + 1) / 2; + p->flags |= TERMP_NOBREAK | TERMP_NOSPACE; + + term_word(p, title); + term_flushln(p); + + p->flags |= TERMP_NOLPAD | TERMP_NOSPACE; + p->offset = p->rmargin; + p->rmargin = p->maxrmargin - strlen(title); + + term_word(p, buf); + term_flushln(p); + + p->offset = p->rmargin; + p->rmargin = p->maxrmargin; + p->flags &= ~TERMP_NOBREAK; + p->flags |= TERMP_NOLPAD | TERMP_NOSPACE; + + term_word(p, title); + term_flushln(p); + + p->rmargin = p->maxrmargin; + p->offset = 0; + p->flags &= ~TERMP_NOSPACE; + + free(title); + free(buf); +} diff --git a/usr.bin/mandoc/man_validate.c b/usr.bin/mandoc/man_validate.c new file mode 100644 index 0000000000..38a72833d5 --- /dev/null +++ b/usr.bin/mandoc/man_validate.c @@ -0,0 +1,280 @@ +/* $Id: man_validate.c,v 1.10 2009/10/21 19:13:50 schwarze Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include + +#include +#include +#include +#include +#include +#include + +#include "libman.h" +#include "libmandoc.h" + +#define CHKARGS struct man *m, const struct man_node *n + +typedef int (*v_check)(CHKARGS); + +struct man_valid { + v_check *pres; + v_check *posts; +}; + +static int check_bline(CHKARGS); +static int check_eq0(CHKARGS); +static int check_le1(CHKARGS); +static int check_ge2(CHKARGS); +static int check_le5(CHKARGS); +static int check_par(CHKARGS); +static int check_part(CHKARGS); +static int check_root(CHKARGS); +static int check_sec(CHKARGS); +static int check_text(CHKARGS); + +static v_check posts_eq0[] = { check_eq0, NULL }; +static v_check posts_ge2_le5[] = { check_ge2, check_le5, NULL }; +static v_check posts_par[] = { check_par, NULL }; +static v_check posts_part[] = { check_part, NULL }; +static v_check posts_sec[] = { check_sec, NULL }; +static v_check posts_sp[] = { check_le1, NULL }; +static v_check pres_bline[] = { check_bline, NULL }; + +static const struct man_valid man_valids[MAN_MAX] = { + { pres_bline, posts_eq0 }, /* br */ + { pres_bline, posts_ge2_le5 }, /* TH */ + { pres_bline, posts_sec }, /* SH */ + { pres_bline, posts_sec }, /* SS */ + { pres_bline, posts_par }, /* TP */ + { pres_bline, posts_par }, /* LP */ + { pres_bline, posts_par }, /* PP */ + { pres_bline, posts_par }, /* P */ + { pres_bline, posts_par }, /* IP */ + { pres_bline, posts_par }, /* HP */ + { NULL, NULL }, /* SM */ + { NULL, NULL }, /* SB */ + { NULL, NULL }, /* BI */ + { NULL, NULL }, /* IB */ + { NULL, NULL }, /* BR */ + { NULL, NULL }, /* RB */ + { NULL, NULL }, /* R */ + { NULL, NULL }, /* B */ + { NULL, NULL }, /* I */ + { NULL, NULL }, /* IR */ + { NULL, NULL }, /* RI */ + { pres_bline, posts_eq0 }, /* na */ + { NULL, NULL }, /* i */ + { pres_bline, posts_sp }, /* sp */ + { pres_bline, posts_eq0 }, /* nf */ + { pres_bline, posts_eq0 }, /* fi */ + { NULL, NULL }, /* r */ + { NULL, NULL }, /* RE */ + { NULL, posts_part }, /* RS */ + { NULL, NULL }, /* DT */ + { NULL, NULL }, /* UC */ +}; + + +int +man_valid_pre(struct man *m, const struct man_node *n) +{ + v_check *cp; + + if (MAN_TEXT == n->type) + return(1); + if (MAN_ROOT == n->type) + return(1); + + if (NULL == (cp = man_valids[n->tok].pres)) + return(1); + for ( ; *cp; cp++) + if ( ! (*cp)(m, n)) + return(0); + return(1); +} + + +int +man_valid_post(struct man *m) +{ + v_check *cp; + + if (MAN_VALID & m->last->flags) + return(1); + m->last->flags |= MAN_VALID; + + switch (m->last->type) { + case (MAN_TEXT): + return(check_text(m, m->last)); + case (MAN_ROOT): + return(check_root(m, m->last)); + default: + break; + } + + if (NULL == (cp = man_valids[m->last->tok].posts)) + return(1); + for ( ; *cp; cp++) + if ( ! (*cp)(m, m->last)) + return(0); + + return(1); +} + + +static int +check_root(CHKARGS) +{ + + if (MAN_BLINE & m->flags) + return(man_nwarn(m, n, WEXITSCOPE)); + if (MAN_ELINE & m->flags) + return(man_nwarn(m, n, WEXITSCOPE)); + + m->flags &= ~MAN_BLINE; + m->flags &= ~MAN_ELINE; + + if (NULL == m->first->child) + return(man_nerr(m, n, WNODATA)); + if (NULL == m->meta.title) + return(man_nerr(m, n, WNOTITLE)); + + return(1); +} + + +static int +check_text(CHKARGS) +{ + const char *p; + int pos, c; + + assert(n->string); + + for (p = n->string, pos = n->pos + 1; *p; p++, pos++) { + if ('\\' == *p) { + c = mandoc_special(p); + if (c) { + p += c - 1; + pos += c - 1; + continue; + } + if ( ! (MAN_IGN_ESCAPE & m->pflags)) + return(man_perr(m, n->line, pos, WESCAPE)); + if ( ! man_pwarn(m, n->line, pos, WESCAPE)) + return(0); + continue; + } + + if ('\t' == *p || isprint((u_char)*p)) + continue; + + if (MAN_IGN_CHARS & m->pflags) + return(man_pwarn(m, n->line, pos, WNPRINT)); + return(man_perr(m, n->line, pos, WNPRINT)); + } + + return(1); +} + + +#define INEQ_DEFINE(x, ineq, name) \ +static int \ +check_##name(CHKARGS) \ +{ \ + if (n->nchild ineq (x)) \ + return(1); \ + return(man_verr(m, n->line, n->pos, \ + "expected line arguments %s %d, have %d", \ + #ineq, (x), n->nchild)); \ +} + +INEQ_DEFINE(0, ==, eq0) +INEQ_DEFINE(1, <=, le1) +INEQ_DEFINE(2, >=, ge2) +INEQ_DEFINE(5, <=, le5) + + +static int +check_sec(CHKARGS) +{ + + if (MAN_BODY == n->type && 0 == n->nchild) + return(man_nwarn(m, n, WBODYARGS)); + if (MAN_HEAD == n->type && 0 == n->nchild) + return(man_nerr(m, n, WHEADARGS)); + return(1); +} + + +static int +check_part(CHKARGS) +{ + + if (MAN_BODY == n->type && 0 == n->nchild) + return(man_nwarn(m, n, WBODYARGS)); + return(1); +} + + +static int +check_par(CHKARGS) +{ + + if (MAN_BODY == n->type) + switch (n->tok) { + case (MAN_IP): + /* FALLTHROUGH */ + case (MAN_HP): + /* FALLTHROUGH */ + case (MAN_TP): + /* Body-less lists are ok. */ + break; + default: + if (n->nchild) + break; + return(man_nwarn(m, n, WBODYARGS)); + } + if (MAN_HEAD == n->type) + switch (n->tok) { + case (MAN_PP): + /* FALLTHROUGH */ + case (MAN_P): + /* FALLTHROUGH */ + case (MAN_LP): + if (0 == n->nchild) + break; + return(man_nwarn(m, n, WNHEADARGS)); + default: + if (n->nchild) + break; + return(man_nwarn(m, n, WHEADARGS)); + } + + return(1); +} + + +static int +check_bline(CHKARGS) +{ + + assert( ! (MAN_ELINE & m->flags)); + if (MAN_BLINE & m->flags) + return(man_nerr(m, n, WLNSCOPE)); + return(1); +} diff --git a/usr.bin/mandoc/mandoc.1 b/usr.bin/mandoc/mandoc.1 new file mode 100644 index 0000000000..ea744ea321 --- /dev/null +++ b/usr.bin/mandoc/mandoc.1 @@ -0,0 +1,416 @@ +.\" $Id: mandoc.1,v 1.17 2009/10/21 19:13:50 schwarze Exp $ +.\" +.\" Copyright (c) 2009 Kristaps Dzonsons +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd October 27, 2009 +.Dt MANDOC 1 +.Os +. +. +.Sh NAME +.Nm mandoc +.Nd format and display UNIX manuals +. +. +.Sh SYNOPSIS +.Nm mandoc +.Op Fl f Ns Ar option... +.Op Fl m Ns Ar format +.Op Fl o Ns Ar option... +.Op Fl T Ns Ar output +.Op Fl V +.Op Fl W Ns Ar err... +.Op Ar infile... +. +. +.Sh DESCRIPTION +The +.Nm +utility formats +.Ux +manual pages for display. The arguments are as follows: +. +.Bl -tag -width Ds +.It Fl f Ns Ar option... +Comma-separated compiler options. See +.Sx Compiler Options +for details. +. +.It Fl m Ns Ar format +Input format. See +.Sx Input Formats +for available formats. Defaults to +.Fl m Ns Ar andoc . +. +.It Fl o Ns Ar option... +Comma-separated output options. See +.Sx Output Options +for details. +. +.It Fl T Ns Ar output +Output format. See +.Sx Output Formats +for available formats. Defaults to +.Fl T Ns Ar ascii . +. +.It Fl V +Print version and exit. +. +.It Fl W Ns Ar err... +Comma-separated warning options. Use +.Fl W Ns Ar all +to print warnings, +.Fl W Ns Ar error +for warnings to be considered errors and cause utility +termination. Multiple +.Fl W +arguments may be comma-separated, such as +.Fl W Ns Ar error,all . +. +.It Ar infile... +Read input from zero or more +.Ar infile . +If unspecified, reads from stdin. If multiple files are specified, +.Nm +will halt with the first failed parse. +.El +. +.Pp +By default, +.Nm +reads +.Xr mdoc 7 +or +.Xr man 7 +text from stdin, implying +.Fl m Ns Ar andoc , +and prints 78-column backspace-encoded output to stdout as if +.Fl T Ns Ar ascii +were provided. +. +.Pp +.Ex -std mandoc +. +. +.Ss Punctuation and Spacing +If punctuation is set apart from words, such as in the phrase +.Dq to be \&, or not to be , +it's processed by +.Nm +according to the following rules: opening punctuation +.Po +.Sq \&( , +.Sq \&[ , +and +.Sq \&{ +.Pc +is not followed by a space; closing punctuation +.Po +.Sq \&. , +.Sq \&, , +.Sq \&; , +.Sq \&: , +.Sq \&? , +.Sq \&! , +.Sq \&) , +.Sq \&] +and +.Sq \&} +.Pc +is not preceded by whitespace. +. +.Pp +If the input is +.Xr mdoc 7 , +these rules are also applied to macro arguments when appropriate. +. +.Pp +White-space, in non-literal (normal) mode, is stripped from input and +replaced on output by a single space. Thus, if you wish to preserve multiple +spaces, they must be space-escaped or used in a literal display mode, e.g., +.Sq \&Bd \-literal +in +.Xr mdoc 7 . +. +. +.Ss Input Formats +The +.Nm +utility accepts +.Xr mdoc 7 +and +.Xr man 7 +input with +.Fl m Ns Ar doc +and +.Fl m Ns Ar an , +respectively. The +.Xr mdoc 7 +format is +.Em strongly +recommended; +.Xr man 7 +should only be used for legacy manuals. +. +.Pp +A third option, +.Fl m Ns Ar andoc , +which is also the default, determines encoding on-the-fly: if the first +non-comment macro is +.Sq \&Dd +or +.Sq \&Dt , +the +.Xr mdoc 7 +parser is used; otherwise, the +.Xr man 7 +parser is used. +. +.Pp +If multiple +files are specified with +.Fl m Ns Ar andoc , +each has its file-type determined this way. If multiple files are +specified and +.Fl m Ns Ar doc +or +.Fl m Ns Ar an +is specified, then this format is used exclusively. +. +. +.Ss Output Formats +The +.Nm +utility accepts the following +.Fl T +arguments: +. +.Bl -tag -width Ds +.It Fl T Ns Ar ascii +Produce 7-bit ASCII output, backspace-encoded for bold and underline +styles. This is the default. +. +.It Fl T Ns Ar html +Produce strict HTML-4.01 output, with a sane default style. +. +.It Fl T Ns Ar tree +Produce an indented parse tree. +. +.It Fl T Ns Ar lint +Parse only: produce no output. +.El +. +.Pp +If multiple input files are specified, these will be processed by the +corresponding filter in-order. +. +. +.Ss Compiler Options +Default compiler behaviour may be overridden with the +.Fl f +flag. +. +.Bl -tag -width Ds +.It Fl f Ns Ar ign-scope +When rewinding the scope of a block macro, forces the compiler to ignore +scope violations. This can seriously mangle the resulting tree. +.Pq mdoc only +. +.It Fl f Ns Ar no-ign-escape +Don't ignore invalid escape sequences. +. +.It Fl f Ns Ar no-ign-macro +Do not ignore unknown macros at the start of input lines. +. +.It Fl f Ns Ar no-ign-chars +Do not ignore disallowed characters. +. +.It Fl f Ns Ar strict +Implies +.Fl f Ns Ar no-ign-escape , +.Fl f Ns Ar no-ign-macro +and +.Fl f Ns Ar no-ign-chars . +. +.It Fl f Ns Ar ign-errors +Don't halt when encountering parse errors. Useful with +.Fl T Ns Ar lint +over a large set of manuals passed on the command line. +.El +. +.Ss Output Options +For the time being, only +.Fl T Ns Ar html +is the only mode with output options: +.Bl -tag -width Ds +.It Fl o Ns Ar style=style.css +The file +.Ar style.css +is used for an external style-sheet. This must be a valid absolute or +relative URI. +.It Fl o Ns Ar includes=fmt +The string +.Ar fmt , +for example, +.Ar ../src/%I.html , +is used as a template for linked header files (usually via the +.Sq \&In +macro). Instances of +.Sq \&%I +are replaced with the include filename. The default is not to present a +hyperlink. +.It Fl o Ns Ar man=fmt +The string +.Ar fmt , +for example, +.Ar ../html%S/%N.%S.html , +is used as a template for linked manuals (usually via the +.Sq \&Xr +macro). Instances of +.Sq \&%N +and +.Sq %S +are replaced with the linked manual's name and section, respectively. +If no section is included, section 1 is assumed. The default is not to +present a hyperlink. +.El +. +.Sh EXAMPLES +To page manuals to the terminal: +. +.Pp +.D1 % mandoc \-Wall,error \-fstrict mandoc.1 2>&1 | less +.D1 % mandoc mandoc.1 mdoc.3 mdoc.7 | less +. +.Pp +To produce HTML manuals with +.Ar style.css +as the style-sheet: +.Pp +.D1 % mandoc \-Thtml -ostyle=style.css mdoc.7 > mdoc.7.html +.Pp +To check over a large set of manuals: +. +.Pp +.Dl % mandoc \-Tlint \-fign-errors `find /usr/src -name \e*\e.[1-9]` +. +. +.Sh COMPATIBILITY +This section summarises +.Nm +compatibility with +.Xr groff 1 . +Each input and output format is separately noted. +. +. +.Ss ASCII output +.Bl -bullet -compact +.It +The +.Sq \e~ +special character doesn't produce expected behaviour in +.Fl T Ns Ar ascii . +. +.It +The +.Sq \&Bd \-literal +and +.Sq \&Bd \-unfilled +macros of +.Xr mdoc 7 +in +.Fl T Ns Ar ascii +are synonyms, as are \-filled and \-ragged. +. +.It +In +.Xr groff 1 , +the +.Sq \&Pa +.Xr mdoc 7 +macro does not underline when scoped under an +.Sq \&It +in the FILES section. This behaves correctly in +.Nm . +. +.It +A list or display following +.Sq \&Ss +.Xr mdoc 7 +macro in +.Fl T Ns Ar ascii +does not assert a prior vertical break, just as it doesn't with +.Sq \&Sh . +. +.It +The +.Sq \&na +.Xr man 7 +macro in +.Fl T Ns Ar ascii +has no effect. +. +.It +Words aren't hyphenated. +. +.It +In normal mode (not a literal block), blocks of spaces aren't preserved, +so double spaces following sentence closure are reduced to a single space; +.Xr groff 1 +retains spaces. +. +.It +Sentences are unilaterally monospaced. +.El +. +.Ss HTML output +.Bl -bullet -compact +.It +The +.Xr mdoc 7 +.Sq \&Bl \-hang +and +.Sq \&Bl \-tag +list types render similarly (no break following overreached left-hand +side) due to the expressive constraints of HTML. +. +.It +The +.Xr man 7 +.Sq IP +and +.Sq TP +lists render similarly. +.El +.\" SECTION +.Sh SEE ALSO +.Xr mandoc_char 7 , +.Xr mdoc 7 , +.Xr man 7 +. +.Sh AUTHORS +The +.Nm +utility was written by +.An Kristaps Dzonsons Aq kristaps@kth.se . +. +.Sh CAVEATS +In +.Fl T Ns Ar html , +the maximum size of an element attribute is determined by +.Dv BUFSIZ , +which is usually 1024 bytes. Be aware of this when setting long link +formats with +.Fl o Ns Ar man=fmt . diff --git a/usr.bin/mandoc/mandoc.c b/usr.bin/mandoc/mandoc.c new file mode 100644 index 0000000000..98e983b111 --- /dev/null +++ b/usr.bin/mandoc/mandoc.c @@ -0,0 +1,104 @@ +/* $Id: mandoc.c,v 1.3 2009/08/22 15:18:11 schwarze Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include + +#include +#include +#include + +#include "libmandoc.h" + +int +mandoc_special(const char *p) +{ + int c; + + if ('\\' != *p++) + return(0); + + switch (*p) { + case ('\\'): + /* FALLTHROUGH */ + case ('\''): + /* FALLTHROUGH */ + case ('`'): + /* FALLTHROUGH */ + case ('q'): + /* FALLTHROUGH */ + case ('-'): + /* FALLTHROUGH */ + case ('~'): + /* FALLTHROUGH */ + case ('^'): + /* FALLTHROUGH */ + case ('%'): + /* FALLTHROUGH */ + case ('0'): + /* FALLTHROUGH */ + case (' '): + /* FALLTHROUGH */ + case ('|'): + /* FALLTHROUGH */ + case ('&'): + /* FALLTHROUGH */ + case ('.'): + /* FALLTHROUGH */ + case (':'): + /* FALLTHROUGH */ + case ('c'): + return(2); + case ('e'): + return(2); + case ('f'): + if (0 == *++p || ! isgraph((u_char)*p)) + return(0); + return(3); + case ('*'): + if (0 == *++p || ! isgraph((u_char)*p)) + return(0); + switch (*p) { + case ('('): + if (0 == *++p || ! isgraph((u_char)*p)) + return(0); + return(4); + case ('['): + for (c = 3, p++; *p && ']' != *p; p++, c++) + if ( ! isgraph((u_char)*p)) + break; + return(*p == ']' ? c : 0); + default: + break; + } + return(3); + case ('('): + if (0 == *++p || ! isgraph((u_char)*p)) + return(0); + if (0 == *++p || ! isgraph((u_char)*p)) + return(0); + return(4); + case ('['): + break; + default: + return(0); + } + + for (c = 3, p++; *p && ']' != *p; p++, c++) + if ( ! isgraph((u_char)*p)) + break; + + return(*p == ']' ? c : 0); +} diff --git a/usr.bin/mandoc/mandoc_char.7 b/usr.bin/mandoc/mandoc_char.7 new file mode 100644 index 0000000000..94b5fc443d --- /dev/null +++ b/usr.bin/mandoc/mandoc_char.7 @@ -0,0 +1,616 @@ +.\" $Id: mandoc_char.7,v 1.7 2009/10/21 19:13:50 schwarze Exp $ +.\" +.\" Copyright (c) 2009 Kristaps Dzonsons +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: October 19 2009 $ +.Dt MANDOC_CHAR 7 +.Os +. +. +.Sh NAME +.Nm mandoc_char +.Nd mandoc special characters +. +. +.Sh DESCRIPTION +This documents the special characters and predefined strings accepted by +.Xr mandoc 1 +to format +.Xr mdoc 7 +and +.Xr man 7 +documents. +. +.Pp +Both +.Xr mdoc 7 +and +.Xr man 7 +encode special characters with +.Sq \eX +.Pq for a one-character escape , +.Sq \e(XX +.Pq two-character , +and +.Sq \e[N] +.Pq N-character . +One may generalise +.Sq \e(XX +as +.Sq \e[XX] +and +.Sq \eX +as +.Sq \e[X] . +Predefined strings are functionally similar to special characters, using +.Sq \e*X +.Pq for a one-character escape , +.Sq \e*(XX +.Pq two-character , +and +.Sq \e*[N] +.Pq N-character . +One may generalise +.Sq \e*(XX +as +.Sq \e*[XX] +and +.Sq \e*X +as +.Sq \e*[X] . +. +.Pp +Note that each output mode will have a different rendering of the +characters. It's guaranteed that each input symbol will correspond to a +(more or less) meaningful output rendering, regardless the mode. +. +.Ss ASCII output +Formatting documents with ASCII output results in a 7-bit ASCII +approximation of zero or more characters, for example, the +.Dq aleph +character +.Sq \e(Ah +will render as +.Sq N . +Approximations are a best-effort, and naturally some clarity will be lost. +. +.Ss HTML output +The HTML output mode uses decimal-encoded UTF-8 for sequences, for +example, the +.Dq aleph +character +.Sq \e(Ah +will render as +.Sq ℵ . +. +. +.Sh SPECIAL CHARACTERS +These are the preferred input symbols for producing special characters. +. +.Pp +Spacing: +.Bl -column -compact -offset indent 10m 20m +.It Em Input Ta Em Description +.It \e~ Ta non-breaking, non-collapsing space +.It \e Ta breaking, non-collapsing n-width space +.It \e^ Ta zero-width space +.It \e% Ta zero-width space +.It \e& Ta zero-width space +.It \e| Ta zero-width space +.It \e0 Ta breaking, non-collapsing digit-width space +.El +. +.Pp +Lines: +.Bl -column -compact -offset indent 10m 10m 10m +.It Em Input Ta Em Rendered Ta Em Description +.It \e(ba Ta \(ba Ta bar +.It \e(br Ta \(br Ta box rule +.It \e(ul Ta \(ul Ta underscore +.It \e(rl Ta \(rl Ta overline +.It \e(bb Ta \(bb Ta broken bar +.It \e(sl Ta \(sl Ta forward slash +.It \e(rs Ta \(rs Ta backward slash +.El +. +.Pp +Text markers: +.Bl -column -compact -offset indent 10m 10m 10m +.It Em Input Ta Em Rendered Ta Em Description +.It \e(ci Ta \(ci Ta circle +.It \e(bu Ta \(bu Ta bullet +.It \e(dd Ta \(dd Ta double dagger +.It \e(dg Ta \(dg Ta dagger +.It \e(lz Ta \(lz Ta lozenge +.It \e(sq Ta \(sq Ta white square +.It \e(ps Ta \(ps Ta paragraph +.It \e(sc Ta \(sc Ta section +.It \e(lh Ta \(lh Ta left hand +.It \e(rh Ta \(rh Ta right hand +.It \e(at Ta \(at Ta at +.It \e(sh Ta \(sh Ta hash (pound) +.It \e(CR Ta \(CR Ta carriage return +.It \e(OK Ta \(OK Ta check mark +.El +. +.Pp +Legal symbols: +.Bl -column -compact -offset indent 10m 10m 10m +.It Em Input Ta Em Rendered Ta Em Description +.It \e(co Ta \(co Ta copyright +.It \e(rg Ta \(rg Ta registered +.It \e(tm Ta \(tm Ta trademarked +.El +. +.Pp +Punctuation: +.Bl -column -compact -offset indent 10m 10m 10m +.It Em Input Ta Em Rendered Ta Em Description +.It \e(em Ta \(em Ta em-dash +.It \e(en Ta \(en Ta en-dash +.It \e(hy Ta \(hy Ta hyphen +.It \e\e Ta \\ Ta back-slash +.It \ee Ta \e Ta back-slash +.It \e. Ta \. Ta period +.It \e(r! Ta \(r! Ta upside-down exclamation +.It \e(r? Ta \(r? Ta upside-down question +.El +. +.Pp +Quotes: +.Bl -column -compact -offset indent 10m 10m 10m +.It Em Input Ta Em Rendered Ta Em Description +.It \e(Bq Ta \(Bq Ta right low double-quote +.It \e(bq Ta \(bq Ta right low single-quote +.It \e(lq Ta \(lq Ta left double-quote +.It \e(rq Ta \(rq Ta right double-quote +.It \e(oq Ta \(oq Ta left single-quote +.It \e(cq Ta \(cq Ta right single-quote +.It \e(aq Ta \(aq Ta apostrophe quote (text) +.It \e(dq Ta \(dq Ta double quote (text) +.It \e(Fo Ta \(Fo Ta left guillemet +.It \e(Fc Ta \(Fc Ta right guillemet +.It \e(fo Ta \(fo Ta left single guillemet +.It \e(fc Ta \(fc Ta right single guillemet +.El +. +.Pp +Brackets: +.Bl -column -compact -offset indent 10m 10m 10m +.It Em Input Ta Em Rendered Ta Em Description +.It \e(lB Ta \(lB Ta left bracket +.It \e(rB Ta \(rB Ta right bracket +.It \e(lC Ta \(lC Ta left brace +.It \e(rC Ta \(rC Ta right brace +.It \e(la Ta \(la Ta left angle +.It \e(ra Ta \(ra Ta right angle +.It \e(bv Ta \(bv Ta brace extension +.It \e[braceex] Ta \[braceex] Ta brace extension +.It \e[bracketlefttp] Ta \[bracketlefttp] Ta top-left hooked bracket +.It \e[bracketleftbp] Ta \[bracketleftbp] Ta bottom-left hooked bracket +.It \e[bracketleftex] Ta \[bracketleftex] Ta left hooked bracket extension +.It \e[bracketrighttp] Ta \[bracketrighttp] Ta top-right hooked bracket +.It \e[bracketrightbp] Ta \[bracketrightbp] Ta bottom-right hooked bracket +.It \e[bracketrightex] Ta \[bracketrightex] Ta right hooked bracket extension +.It \e(lt Ta \(lt Ta top-left hooked brace +.It \e[bracelefttp] Ta \[bracelefttp] Ta top-left hooked brace +.It \e(lk Ta \(lk Ta mid-left hooked brace +.It \e[braceleftmid] Ta \[braceleftmid] Ta mid-left hooked brace +.It \e(lb Ta \(lb Ta bottom-left hooked brace +.It \e[braceleftbp] Ta \[braceleftbp] Ta bottom-left hooked brace +.It \e[braceleftex] Ta \[braceleftex] Ta left hooked brace extension +.It \e(rt Ta \(rt Ta top-left hooked brace +.It \e[bracerighttp] Ta \[bracerighttp] Ta top-right hooked brace +.It \e(rk Ta \(rk Ta mid-right hooked brace +.It \e[bracerightmid] Ta \[bracerightmid] Ta mid-right hooked brace +.It \e(rb Ta \(rb Ta bottom-right hooked brace +.It \e[bracerightbp] Ta \[bracerightbp] Ta bottom-right hooked brace +.It \e[bracerightex] Ta \[bracerightex] Ta right hooked brace extension +.It \e[parenlefttp] Ta \[parenlefttp] Ta top-left hooked parenthesis +.It \e[parenleftbp] Ta \[parenleftbp] Ta bottom-left hooked parenthesis +.It \e[parenleftex] Ta \[parenleftex] Ta left hooked parenthesis extension +.It \e[parenrighttp] Ta \[parenrighttp] Ta top-right hooked parenthesis +.It \e[parenrightbp] Ta \[parenrightbp] Ta bottom-right hooked parenthesis +.It \e[parenrightex] Ta \[parenrightex] Ta right hooked parenthesis extension +.El +. +.Pp +Arrows: +.Bl -column -compact -offset indent 10m 10m 10m +.It Em Input Ta Em Rendered Ta Em Description +.It \e(<- Ta \(<- Ta left arrow +.It \e(-> Ta \(-> Ta right arrow +.It \e(<> Ta \(<> Ta left-right arrow +.It \e(da Ta \(da Ta down arrow +.It \e(ua Ta \(ua Ta up arrow +.It \e(va Ta \(va Ta up-down arrow +.It \e(lA Ta \(lA Ta left double-arrow +.It \e(rA Ta \(rA Ta right double-arrow +.It \e(hA Ta \(hA Ta left-right double-arrow +.It \e(uA Ta \(uA Ta up double-arrow +.It \e(dA Ta \(dA Ta down double-arrow +.It \e(vA Ta \(vA Ta up-down double-arrow +.El +. +.Pp +Logical: +.Bl -column -compact -offset indent 10m 10m 10m +.It Em Input Ta Em Rendered Ta Em Description +.It \e(AN Ta \(AN Ta logical and +.It \e(OR Ta \(OR Ta logical or +.It \e(no Ta \(no Ta logical not +.It \e[tno] Ta \[tno] Ta logical not (text) +.It \e(te Ta \(te Ta existential quantifier +.It \e(fa Ta \(fa Ta universal quantifier +.It \e(st Ta \(st Ta such that +.It \e(tf Ta \(tf Ta therefore +.It \e(3d Ta \(3d Ta therefore +.It \e(or Ta \(or Ta bitwise or +.El +. +.Pp +Mathematical: +.Bl -column -compact -offset indent 10m 10m 10m +.It Em Input Ta Em Rendered Ta Em Description +.It \e(pl Ta \(pl Ta plus +.It \e(mi Ta \(mi Ta minus +.It \e- Ta \- Ta minus (text) +.It \e(-+ Ta \(-+ Ta minus-plus +.It \e(+- Ta \(+- Ta plus-minus +.It \e[t+-] Ta \[t+-] Ta plus-minus (text) +.It \e(pc Ta \(pc Ta centre-dot +.It \e(mu Ta \(mu Ta multiply +.It \e[tmu] Ta \[tmu] Ta multiply (text) +.It \e(c* Ta \(c* Ta circle-multiply +.It \e(c+ Ta \(c+ Ta circle-plus +.It \e(di Ta \(di Ta divide +.It \e[tdi] Ta \[tdi] Ta divide (text) +.It \e(f/ Ta \(f/ Ta fraction +.It \e(** Ta \(** Ta asterisk +.It \e(<= Ta \(<= Ta less-than-equal +.It \e(>= Ta \(>= Ta greater-than-equal +.It \e(<< Ta \(<< Ta much less +.It \e(>> Ta \(>> Ta much greater +.It \e(eq Ta \(eq Ta equal +.It \e(!= Ta \(!= Ta not equal +.It \e(== Ta \(== Ta equivalent +.It \e(ne Ta \(ne Ta not equivalent +.It \e(=~ Ta \(=~ Ta congruent +.It \e(-~ Ta \(-~ Ta asymptotically congruent +.It \e(ap Ta \(ap Ta asymptotically similar +.It \e(~~ Ta \(~~ Ta approximately similar +.It \e(~= Ta \(~= Ta approximately equal +.It \e(pt Ta \(pt Ta proportionate +.It \e(es Ta \(es Ta empty set +.It \e(mo Ta \(mo Ta element +.It \e(nm Ta \(nm Ta not element +.It \e(sb Ta \(sb Ta proper subset +.It \e(nb Ta \(nb Ta not subset +.It \e(sp Ta \(sp Ta proper superset +.It \e(nc Ta \(nc Ta not superset +.It \e(ib Ta \(ib Ta reflexive subset +.It \e(ip Ta \(ip Ta reflexive superset +.It \e(ca Ta \(ca Ta intersection +.It \e(cu Ta \(cu Ta union +.It \e(/_ Ta \(/_ Ta angle +.It \e(pp Ta \(pp Ta perpendicular +.It \e(is Ta \(is Ta integral +.It \e[integral] Ta \[integral] Ta integral +.It \e[sum] Ta \[sum] Ta summation +.It \e[product] Ta \[product] Ta product +.It \e[coproduct] Ta \[coproduct] Ta coproduct +.It \e(gr Ta \(gr Ta gradient +.It \e(sr Ta \(sr Ta square root +.It \e[sqrt] Ta \[sqrt] Ta square root +.It \e(lc Ta \(lc Ta left-ceiling +.It \e(rc Ta \(rc Ta right-ceiling +.It \e(lf Ta \(lf Ta left-floor +.It \e(rf Ta \(rf Ta right-floor +.It \e(if Ta \(if Ta infinity +.It \e(Ah Ta \(Ah Ta aleph +.It \e(Im Ta \(Im Ta imaginary +.It \e(Re Ta \(Re Ta real +.It \e(pd Ta \(pd Ta partial differential +.It \e(-h Ta \(-h Ta Planck constant over 2\(*p +.El +. +.Pp +Ligatures: +.Bl -column -compact -offset indent 10m 10m 10m +.It Em Input Ta Em Rendered Ta Em Description +.It \e(ff Ta \(ff Ta ff ligature +.It \e(fi Ta \(fi Ta fi ligature +.It \e(fl Ta \(fl Ta fl ligature +.It \e(Fi Ta \(Fi Ta ffi ligature +.It \e(Fl Ta \(Fl Ta ffl ligature +.It \e(AE Ta \(AE Ta AE +.It \e(ae Ta \(ae Ta ae +.It \e(OE Ta \(OE Ta OE +.It \e(oe Ta \(oe Ta oe +.It \e(ss Ta \(ss Ta German eszett +.It \e(IJ Ta \(IJ Ta IJ ligature +.It \e(ij Ta \(ij Ta ij ligature +.El +. +.Pp +Accents: +.Bl -column -compact -offset indent 10m 10m 10m +.It Em Input Ta Em Rendered Ta Em Description +.It \e(a" Ta \(a" Ta Hungarian umlaut +.It \e(a- Ta \(a- Ta macron +.It \e(a. Ta \(a. Ta dotted +.It \e(a^ Ta \(a^ Ta circumflex +.It \e(aa Ta \(aa Ta acute +.It \e' Ta \' Ta acute +.It \e(ga Ta \(ga Ta grave +.It \e` Ta \` Ta grave +.It \e(ab Ta \(ab Ta breve +.It \e(ac Ta \(ac Ta cedilla +.It \e(ad Ta \(ad Ta dieresis +.It \e(ah Ta \(ah Ta caron +.It \e(ao Ta \(ao Ta ring +.It \e(a~ Ta \(a~ Ta tilde +.It \e(ho Ta \(ho Ta ogonek +.It \e(ha Ta \(ha Ta hat (text) +.It \e(ti Ta \(ti Ta tilde (text) +.El +. +.Pp +Accented letters: +.Bl -column -compact -offset indent 10m 10m 10m +.It Em Input Ta Em Rendered Ta Em Description +.It \e('A Ta \('A Ta acute A +.It \e('E Ta \('E Ta acute E +.It \e('I Ta \('I Ta acute I +.It \e('O Ta \('O Ta acute O +.It \e('U Ta \('U Ta acute U +.It \e('a Ta \('a Ta acute a +.It \e('e Ta \('e Ta acute e +.It \e('i Ta \('i Ta acute i +.It \e('o Ta \('o Ta acute o +.It \e('u Ta \('u Ta acute u +.It \e(`A Ta \(`A Ta grave A +.It \e(`E Ta \(`E Ta grave E +.It \e(`I Ta \(`I Ta grave I +.It \e(`O Ta \(`O Ta grave O +.It \e(`U Ta \(`U Ta grave U +.It \e(`a Ta \(`a Ta grave a +.It \e(`e Ta \(`e Ta grave e +.It \e(`i Ta \(`i Ta grave i +.It \e(`o Ta \(`i Ta grave o +.It \e(`u Ta \(`u Ta grave u +.It \e(~A Ta \(~A Ta tilde A +.It \e(~N Ta \(~N Ta tilde N +.It \e(~O Ta \(~O Ta tilde O +.It \e(~a Ta \(~a Ta tilde a +.It \e(~n Ta \(~n Ta tilde n +.It \e(~o Ta \(~o Ta tilde o +.It \e(:A Ta \(:A Ta dieresis A +.It \e(:E Ta \(:E Ta dieresis E +.It \e(:I Ta \(:I Ta dieresis I +.It \e(:O Ta \(:O Ta dieresis O +.It \e(:U Ta \(:U Ta dieresis U +.It \e(:a Ta \(:a Ta dieresis a +.It \e(:e Ta \(:e Ta dieresis e +.It \e(:i Ta \(:i Ta dieresis i +.It \e(:o Ta \(:o Ta dieresis o +.It \e(:u Ta \(:u Ta dieresis u +.It \e(:y Ta \(:y Ta dieresis y +.It \e(^A Ta \(^A Ta circumflex A +.It \e(^E Ta \(^E Ta circumflex E +.It \e(^I Ta \(^I Ta circumflex I +.It \e(^O Ta \(^O Ta circumflex O +.It \e(^U Ta \(^U Ta circumflex U +.It \e(^a Ta \(^a Ta circumflex a +.It \e(^e Ta \(^e Ta circumflex e +.It \e(^i Ta \(^i Ta circumflex i +.It \e(^o Ta \(^o Ta circumflex o +.It \e(^u Ta \(^u Ta circumflex u +.It \e(,C Ta \(,C Ta cedilla C +.It \e(,c Ta \(,c Ta cedilla c +.It \e(/L Ta \(/L Ta stroke L +.It \e(/l Ta \(/l Ta stroke l +.It \e(/O Ta \(/O Ta stroke O +.It \e(/o Ta \(/o Ta stroke o +.It \e(oA Ta \(oA Ta ring A +.It \e(oa Ta \(oa Ta ring a +.El +. +.Pp +Special letters: +.Bl -column -compact -offset indent 10m 10m 10m +.It Em Input Ta Em Rendered Ta Em Description +.It \e(-D Ta \(-D Ta Eth +.It \e(Sd Ta \(Sd Ta eth +.It \e(TP Ta \(TP Ta Thorn +.It \e(Tp Ta \(Tp Ta thorn +.It \e(.i Ta \(.i Ta dotless i +.It \e(.j Ta \(.j Ta dotless j +.El +. +.Pp +Currency: +.Bl -column -compact -offset indent 10m 10m 10m +.It Em Input Ta Em Rendered Ta Em Description +.It \e(Do Ta \(Do Ta dollar +.It \e(ct Ta \(ct Ta cent +.It \e(Eu Ta \(Eu Ta Euro symbol +.It \e(eu Ta \(eu Ta Euro symbol +.It \e(Ye Ta \(Ye Ta yen +.It \e(Po Ta \(Po Ta pound +.It \e(Cs Ta \(Cs Ta Scandinavian +.It \e(Fn Ta \(Fn Ta florin +.El +. +.Pp +Units: +.Bl -column -compact -offset indent 10m 10m 10m +.It Em Input Ta Em Rendered Ta Em Description +.It \e(de Ta \(de Ta degree +.It \e(%0 Ta \(%0 Ta per-thousand +.It \e(fm Ta \(fm Ta minute +.It \e(sd Ta \(sd Ta second +.It \e(mc Ta \(mc Ta micro +.El +. +.Pp +Greek letters: +.Bl -column -compact -offset indent 10m 10m 10m +.It Em Input Ta Em Rendered Ta Em Description +.It \e(*A Ta \(*A Ta Alpha +.It \e(*B Ta \(*B Ta Beta +.It \e(*G Ta \(*G Ta Gamma +.It \e(*D Ta \(*D Ta Delta +.It \e(*E Ta \(*E Ta Epsilon +.It \e(*Z Ta \(*Z Ta Zeta +.It \e(*Y Ta \(*Y Ta Eta +.It \e(*H Ta \(*H Ta Theta +.It \e(*I Ta \(*I Ta Iota +.It \e(*K Ta \(*K Ta Kappa +.It \e(*L Ta \(*L Ta Lambda +.It \e(*M Ta \(*M Ta Mu +.It \e(*N Ta \(*N Ta Nu +.It \e(*C Ta \(*C Ta Xi +.It \e(*O Ta \(*O Ta Omicron +.It \e(*P Ta \(*P Ta Pi +.It \e(*R Ta \(*R Ta Rho +.It \e(*S Ta \(*S Ta Sigma +.It \e(*T Ta \(*T Ta Tau +.It \e(*U Ta \(*U Ta Upsilon +.It \e(*F Ta \(*F Ta Phi +.It \e(*X Ta \(*X Ta Chi +.It \e(*Q Ta \(*Q Ta Psi +.It \e(*W Ta \(*W Ta Omega +.It \e(*a Ta \(*a Ta alpha +.It \e(*b Ta \(*b Ta beta +.It \e(*g Ta \(*g Ta gamma +.It \e(*d Ta \(*d Ta delta +.It \e(*e Ta \(*e Ta epsilon +.It \e(*z Ta \(*z Ta zeta +.It \e(*y Ta \(*y Ta eta +.It \e(*h Ta \(*h Ta theta +.It \e(*i Ta \(*i Ta iota +.It \e(*k Ta \(*k Ta kappa +.It \e(*l Ta \(*l Ta lambda +.It \e(*m Ta \(*m Ta mu +.It \e(*n Ta \(*n Ta nu +.It \e(*c Ta \(*c Ta xi +.It \e(*o Ta \(*o Ta omicron +.It \e(*p Ta \(*p Ta pi +.It \e(*r Ta \(*r Ta rho +.It \e(*s Ta \(*s Ta sigma +.It \e(*t Ta \(*t Ta tau +.It \e(*u Ta \(*u Ta upsilon +.It \e(*f Ta \(*f Ta phi +.It \e(*x Ta \(*x Ta chi +.It \e(*q Ta \(*q Ta psi +.It \e(*w Ta \(*w Ta omega +.It \e(+h Ta \(+h Ta theta variant +.It \e(+f Ta \(+f Ta phi variant +.It \e(+p Ta \(+p Ta pi variant +.It \e(+e Ta \(+e Ta epsilon variant +.It \e(ts Ta \(ts Ta sigma terminal +.El +. +. +.Sh PREDEFINED STRINGS +These are not recommended for use, as they differ across +implementations: +. +.Pp +.Bl -column -compact -offset indent 10m 10m 10m +.It Em Input Ta Em Rendered Ta Em Description +.It \e*(Ba Ta \*(Ba Ta vertical bar +.It \e*(Ne Ta \*(Ne Ta not equal +.It \e*(Ge Ta \*(Ge Ta greater-than-equal +.It \e*(Le Ta \*(Le Ta less-than-equal +.It \e*(Gt Ta \*(Gt Ta greater-than +.It \e*(Lt Ta \*(Lt Ta less-than +.It \e*(Pm Ta \*(Pm Ta plus-minus +.It \e*(If Ta \*(If Ta infinity +.It \e*(Pi Ta \*(Pi Ta pi +.It \e*(Na Ta \*(Na Ta NaN +.It \e*(Am Ta \*(Am Ta ampersand +.It \e*R Ta \*R Ta restricted mark +.It \e*(Tm Ta \*(Tm Ta trade mark +.It \e*q Ta \*q Ta double-quote +.It \e*(Rq Ta \*(Rq Ta right-double-quote +.It \e*(Lq Ta \*(Lq Ta left-double-quote +.It \e*(lp Ta \*(lp Ta right-parenthesis +.It \e*(rp Ta \*(rp Ta left-parenthesis +.It \e*(lq Ta \*(lq Ta left double-quote +.It \e*(rq Ta \*(rq Ta right double-quote +.It \e*(ua Ta \*(ua Ta up arrow +.It \e*(va Ta \*(va Ta up-down arrow +.It \e*(<= Ta \*(<= Ta less-than-equal +.It \e*(>= Ta \*(>= Ta greater-than-equal +.It \e*(aa Ta \*(aa Ta acute +.It \e*(ga Ta \*(ga Ta grave +.El +. +. +.Sh COMPATIBILITY +This section documents compatibility of +.Nm +with older or existing versions of +.Xr groff 1 . +. +.Pp +The following render differently in +.Fl T Ns Ar ascii +output mode: +.Bd -ragged -offset indent +\e(ss, \e(nm, \e(nb, \e(nc, \e(ib, \e(ip, \e(pp, \e[sum], \e[product], +\e[coproduct], \e(gr, \e(-h, \e(a. +.Ed +. +.Pp +The following render differently in +.Fl T Ns Ar html +output mode: +.Bd -ragged -offset indent +\e(~=, \e(nb, \e(nc +.Ed +. +.Pp +Finally, the following have been omitted by being poorly documented or +having no known representation: +.Bd -ragged -offset indent +\e[radicalex], \e[sqrtex], \e(ru +.Ed +. +. +.Sh SEE ALSO +.Xr mandoc 1 +. +. +.Sh STANDARDS +.Rs +.%A The Unicode Consortium +.%T The Unicode Standard: Worldwide Character Encoding, Version 5.2 +.%D 1991 +.Re +.Rs +.%A W3C +.%T HTML 4.01 Specification +.%D December, 1999 +.Re +. +. +.Sh AUTHORS +The +.Nm +utility was written by +.An Kristaps Dzonsons Aq kristaps@kth.se . diff --git a/usr.bin/mandoc/manuals.7 b/usr.bin/mandoc/manuals.7 new file mode 100644 index 0000000000..b3f565bde2 --- /dev/null +++ b/usr.bin/mandoc/manuals.7 @@ -0,0 +1,236 @@ +.\" $Id: manuals.7,v 1.5 2009/08/22 16:32:22 schwarze Exp $ +.\" +.\" Copyright (c) 2009 Kristaps Dzonsons +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: July 26 2009 $ +.Dt MANUALS 7 +.Os +.\" SECTION +.Sh NAME +.Nm Writing UNIX Documentation +.Nd a guide to writing UNIX manuals +.\" SECTION +.Sh DESCRIPTION +.Em A utility without good documentation is of no utility at all . +.\" PARAGRAPH +.Pp +A system component's documentation describes the utility of that +component, whether it's a device driver, an executable or, most +importantly, a game. +.Pp +This document serves as a tutorial to writing +.Ux +documentation +.Pq Dq manuals . +.\" SECTION +.Sh ENVIRONMENT +First, copy over the manual template from +.Pa /usr/share/misc/mdoc.template +into your source directory. +.Pp +.Dl % cp /usr/share/misc/mdoc.template \. +.Pp +.Em \&Do not +start afresh or by copying another manual unless you know exactly what +you're doing! If the template doesn't exist, bug your administrator. +.\" SUBSECTION +.Ss Section Numbering +Find an appropriate section for your manual. There may exist multiple +manual names per section, so be specific: +.Pp +.\" LIST +.Bl -tag -width "XXXXXXXXXXXX" -offset indent -compact +.It Em Section +.Em Description +.It 1 +operator utilities +.It 2 +system calls +.It 3, 3p, 3f +programming libraries (C, Perl, Fortran) +.It 5 +file and wire protocol formats +.It 6 +games +.It 7 +tutorials, documents and papers +.It 8 +administrator utilities +.It 9 +in-kernel routines +.El +.Pp +If your manual falls into multiple categories, choose the most +widely-used or, better, re-consider the topic of your manual to be more +specific. You can list all manuals per section by invoking +.Xr apropos 1 , +then provide the +.Fl s +flag to +.Xr man 1 +to see the specific section manual (section 1, in this example): +.\" DISPLAY +.Bd -literal -offset indent +% apropos myname +myname (1) - utility description +myname (3) - library description +% man \-s 1 myname +.Ed +.\" SUBSECTION +.Ss Naming +Name your component. Be terse, erring on the side of clarity. Look for +other manuals by that same name before committing: +.Pp +.Dl % apropos myname +.Pp +Manual files are named +.Pa myname.mysection , +such as +.Pa manuals.7 +for this document. Rename the template file: +.Pp +.Dl % mv mdoc.template myname.mysection +.\" SUBSECTION +.Ss Development Tools +While writing, make sure that your manual is correctly structured: +.Pp +.Dl % mandoc \-Tlint \-Wall \-fstrict name.1 +.Pp +The quick-fix feature of +.Xr vim 1 +is useful for checking over many manuals: +.Bd -literal -offset indent +% mandoc \-Wall \-fstrict \-Tlint \-fign-errors \e + ./path/to/manuals/* 2>&1 > /tmp/mandoc.errs +% vim -q /tmp/mandoc.errs +.Ed +.Pp +You may spell-check your work as follows: +.Pp +.Dl % deroff name.1 | spell +.Pp +If +.Xr ispell 1 +is installed, it has a special mode for manuals: +.Pp +.Dl % ispell \-n name.1 +.Pp +Use +.Xr cvs 1 +or +.Xr rcs 1 +to version-control your work. If you wish the last check-in to effect +your document's date, use the following RCS tag for the date macro: +.Pp +.Dl \&.Dd $Mdocdate: July 26 2009 $ +.\" SUBSECTION +.Ss Viewing +mdoc documents may be paged to your terminal with +.Xr mandoc 1 . +If you plan on distributing your work to systems without this tool, +check it against +.Xr groff 1 : +.Bd -literal -offset indent +% mandoc \-Wall name.1 2>&1 | less +% groff -mandoc name.1 2>&1 | less +.Ed +.\" SUBSECTION +.Ss Automation +Consider adding your mdoc documents to +.Xr make 1 +Makefiles in order to automatically check your input: +.Bd -literal -offset indent +\&.SUFFIXES: .1 .in + +\&.in.1: + mandoc -Wall,error -Tlint $< + cp -f $< $@ +.Ed +.\" SUBSECTION +.Ss Licensing +Your manual must have a license. It should be listed at the start of +your document, just as in source code. +.\" SECTION +.Sh COMPOSITION +Manuals should +.Em always +be written in the +.Xr mdoc 7 +formatting language. +.\" PARAGRAPH +.Pp +Open the template you've copied into +.Pa myname.mysection +and begin editing. +.\" SUBSECTION +.Ss Language +.Bl -enum +.It +Use clear, concise language. Favour simplicity. +.It +Write your manual in non-idiomatic English. Don't worry about +Commonwealth or American spellings \(em just correct ones. +.It +Spell-check your manual, keeping in mind short-letter terms ( +.Xr iwi 4 +vs. +.Xr iwn 4 ) . +.It +If you absolutely must use special characters (diacritics, mathematical +symbols and so on), use the escapes dictated in +.Xr mdoc 7 . +.El +.\" SUBSECTION +.Ss Style +The structure of the mdoc language makes it very hard to have any +particular format style. Keep your lines under 72 characters in length. +If you must have long option lines, use +.Sq \&Oo/Oc . +The same goes for function prototypes. +.Em \&Do not +use +.Sq \&Xo/Xc . +Find another way to structure your line. +.\" SUBSECTION +.Ss References +Other components may be referenced with the +.Sq \&Xr +and +.Sq \&Sx +macros. Make sure that these exist. If you intend to distribute your +manual, make sure +.Sq \&Xr +references are valid across systems (within reason). If you cross-link with +.Sq \&Sx , +make sure that the section reference exists. +.\" SUBSECTION +.Ss Citations +Cite your work. If your system references standards documents or other +publications, please use the +.Sq \&Rs/Re +block macros. +.\" SUBSECTION +.Ss Formatting +.Em Don't style your manual . +Give it meaningful content. The front-end will worry about formatting +and style. +.\" SECTION +.Sh MAINTENANCE +As your component changes and bugs are fixed, your manual may become out +of date. You may be tempted to use tools like Doxygen to automate the +development of your manuals. Don't. +.Pp +.Em Manuals are part of a system component : +if you modify your code or specifications, modify the documentation. diff --git a/usr.bin/mandoc/mdoc.3 b/usr.bin/mandoc/mdoc.3 new file mode 100644 index 0000000000..2eb86c87c1 --- /dev/null +++ b/usr.bin/mandoc/mdoc.3 @@ -0,0 +1,336 @@ +.\" $Id: mdoc.3,v 1.5 2009/10/20 10:15:04 schwarze Exp $ +.\" +.\" Copyright (c) 2009 Kristaps Dzonsons +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: August 9 2009 $ +.Dt MDOC 3 +.Os +.\" SECTION +.Sh NAME +.Nm mdoc_alloc , +.Nm mdoc_parseln , +.Nm mdoc_endparse , +.Nm mdoc_node , +.Nm mdoc_meta , +.Nm mdoc_free , +.Nm mdoc_reset +.Nd mdoc macro compiler library +.\" SECTION +.Sh SYNOPSIS +.In mdoc.h +.Vt extern const char * const * mdoc_macronames; +.Vt extern const char * const * mdoc_argnames; +.Ft "struct mdoc *" +.Fn mdoc_alloc "void *data" "int pflags" "const struct mdoc_cb *cb" +.Ft int +.Fn mdoc_reset "struct mdoc *mdoc" +.Ft void +.Fn mdoc_free "struct mdoc *mdoc" +.Ft int +.Fn mdoc_parseln "struct mdoc *mdoc" "int line" "char *buf" +.Ft "const struct mdoc_node *" +.Fn mdoc_node "const struct mdoc *mdoc" +.Ft "const struct mdoc_meta *" +.Fn mdoc_meta "const struct mdoc *mdoc" +.Ft int +.Fn mdoc_endparse "struct mdoc *mdoc" +.\" SECTION +.Sh DESCRIPTION +The +.Nm mdoc +library parses lines of +.Xr mdoc 7 +input (and +.Em only +mdoc) into an abstract syntax tree (AST). +.\" PARAGRAPH +.Pp +In general, applications initiate a parsing sequence with +.Fn mdoc_alloc , +parse each line in a document with +.Fn mdoc_parseln , +close the parsing session with +.Fn mdoc_endparse , +operate over the syntax tree returned by +.Fn mdoc_node +and +.Fn mdoc_meta , +then free all allocated memory with +.Fn mdoc_free . +The +.Fn mdoc_reset +function may be used in order to reset the parser for another input +sequence. See the +.Sx EXAMPLES +section for a full example. +.\" PARAGRAPH +.Pp +This section further defines the +.Sx Types , +.Sx Functions +and +.Sx Variables +available to programmers. Following that, the +.Sx Abstract Syntax Tree +section documents the output tree. +.\" SUBSECTION +.Ss Types +Both functions (see +.Sx Functions ) +and variables (see +.Sx Variables ) +may use the following types: +.Bl -ohang -offset "XXXX" +.\" LIST-ITEM +.It Vt struct mdoc +An opaque type defined in +.Pa mdoc.c . +Its values are only used privately within the library. +.\" LIST-ITEM +.It Vt struct mdoc_cb +A set of message callbacks defined in +.Pa mdoc.h . +.\" LIST-ITEM +.It Vt struct mdoc_node +A parsed node. Defined in +.Pa mdoc.h . +See +.Sx Abstract Syntax Tree +for details. +.El +.\" SUBSECTION +.Ss Functions +Function descriptions follow: +.Bl -ohang -offset "XXXX" +.\" LIST-ITEM +.It Fn mdoc_alloc +Allocates a parsing structure. The +.Fa data +pointer is passed to callbacks in +.Fa cb , +which are documented further in the header file. +The +.Fa pflags +arguments are defined in +.Pa mdoc.h . +Returns NULL on failure. If non-NULL, the pointer must be freed with +.Fn mdoc_free . +.\" LIST-ITEM +.It Fn mdoc_reset +Reset the parser for another parse routine. After its use, +.Fn mdoc_parseln +behaves as if invoked for the first time. If it returns 0, memory could +not be allocated. +.\" LIST-ITEM +.It Fn mdoc_free +Free all resources of a parser. The pointer is no longer valid after +invocation. +.\" LIST-ITEM +.It Fn mdoc_parseln +Parse a nil-terminated line of input. This line should not contain the +trailing newline. Returns 0 on failure, 1 on success. The input buffer +.Fa buf +is modified by this function. +.\" LIST-ITEM +.It Fn mdoc_endparse +Signals that the parse is complete. Note that if +.Fn mdoc_endparse +is called subsequent to +.Fn mdoc_node , +the resulting tree is incomplete. Returns 0 on failure, 1 on success. +.\" LIST-ITEM +.It Fn mdoc_node +Returns the first node of the parse. Note that if +.Fn mdoc_parseln +or +.Fn mdoc_endparse +return 0, the tree will be incomplete. +.It Fn mdoc_meta +Returns the document's parsed meta-data. If this information has not +yet been supplied or +.Fn mdoc_parseln +or +.Fn mdoc_endparse +return 0, the data will be incomplete. +.El +.\" SUBSECTION +.Ss Variables +The following variables are also defined: +.Bl -ohang -offset "XXXX" +.\" LIST-ITEM +.It Va mdoc_macronames +An array of string-ified token names. +.\" LIST-ITEM +.It Va mdoc_argnames +An array of string-ified token argument names. +.El +.\" SUBSECTION +.Ss Abstract Syntax Tree +The +.Nm +functions produce an abstract syntax tree (AST) describing input in a +regular form. It may be reviewed at any time with +.Fn mdoc_nodes ; +however, if called before +.Fn mdoc_endparse , +or after +.Fn mdoc_endparse +or +.Fn mdoc_parseln +fail, it may be incomplete. +.\" PARAGRAPH +.Pp +This AST is governed by the ontological +rules dictated in +.Xr mdoc 7 +and derives its terminology accordingly. +.Qq In-line +elements described in +.Xr mdoc 7 +are described simply as +.Qq elements . +.\" PARAGRAPH +.Pp +The AST is composed of +.Vt struct mdoc_node +nodes with block, head, body, element, root and text types as declared +by the +.Va type +field. Each node also provides its parse point (the +.Va line , +.Va sec , +and +.Va pos +fields), its position in the tree (the +.Va parent , +.Va child , +.Va next +and +.Va prev +fields) and some type-specific data. +.\" PARAGRAPH +.Pp +The tree itself is arranged according to the following normal form, +where capitalised non-terminals represent nodes. +.Pp +.Bl -tag -width "ELEMENTXX" -compact -offset "XXXX" +.\" LIST-ITEM +.It ROOT +\(<- mnode+ +.It mnode +\(<- BLOCK | ELEMENT | TEXT +.It BLOCK +\(<- (HEAD [TEXT])+ [BODY [TEXT]] [TAIL [TEXT]] +.It BLOCK +\(<- BODY [TEXT] [TAIL [TEXT]] +.It ELEMENT +\(<- TEXT* +.It HEAD +\(<- mnode+ +.It BODY +\(<- mnode+ +.It TAIL +\(<- mnode+ +.It TEXT +\(<- [[:alpha:]]* +.El +.\" PARAGRAPH +.Pp +Of note are the TEXT nodes following the HEAD, BODY and TAIL nodes of +the BLOCK production. These refer to punctuation marks. Furthermore, +although a TEXT node will generally have a non-zero-length string, in +the specific case of +.Sq \&.Bd \-literal , +an empty line will produce a zero-length string. +.\" SECTION +.Sh EXAMPLES +The following example reads lines from stdin and parses them, operating +on the finished parse tree with +.Fn parsed . +Note that, if the last line of the file isn't newline-terminated, this +will truncate the file's last character (see +.Xr fgetln 3 ) . +Further, this example does not error-check nor free memory upon failure. +.Bd -literal -offset "XXXX" +struct mdoc *mdoc; +const struct mdoc_node *node; +char *buf; +size_t len; +int line; + +line = 1; +mdoc = mdoc_alloc(NULL, 0, NULL); + +while ((buf = fgetln(fp, &len))) { + buf[len - 1] = '\\0'; + if ( ! mdoc_parseln(mdoc, line, buf)) + errx(1, "mdoc_parseln"); + line++; +} + +if ( ! mdoc_endparse(mdoc)) + errx(1, "mdoc_endparse"); +if (NULL == (node = mdoc_node(mdoc))) + errx(1, "mdoc_node"); + +parsed(mdoc, node); +mdoc_free(mdoc); +.Ed +.\" SECTION +.Sh SEE ALSO +.Xr mandoc 1 , +.Xr mdoc 7 +.\" SECTION +.Sh AUTHORS +The +.Nm +utility was written by +.An Kristaps Dzonsons Aq kristaps@kth.se . +.\" SECTION +.Sh CAVEATS +.Bl -dash -compact +.\" LIST-ITEM +.It +The +.Sq \&.Xc +and +.Sq \&.Xo +macros aren't handled when used to span lines for the +.Sq \&.It +macro. +.\" LIST-ITEM +.It +The +.Sq \&.Bsx +macro family doesn't yet understand version arguments. +.\" LIST-ITEM +.It +If not given a value, the \-offset argument to +.Sq \&.Bd +and +.Sq \&.Bl +should be the width of +.Qq ; +instead, a value of +.Li 10n +is provided. +.\" LIST-ITEM +.It +Columns widths in +.Sq \&.Bl \-column +should default to width +.Qq +if not included. +.El diff --git a/usr.bin/mandoc/mdoc.7 b/usr.bin/mandoc/mdoc.7 new file mode 100644 index 0000000000..47ff812e64 --- /dev/null +++ b/usr.bin/mandoc/mdoc.7 @@ -0,0 +1,1775 @@ +.\" $Id: mdoc.7,v 1.17 2009/10/21 19:13:50 schwarze Exp $ +.\" +.\" Copyright (c) 2009 Kristaps Dzonsons +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: October 19 2009 $ +.Dt MDOC 7 +.Os +. +. +.Sh NAME +.Nm mdoc +.Nd mdoc language reference +. +. +.Sh DESCRIPTION +The +.Nm mdoc +language is used to format +.Bx +.Ux +manuals. In this reference document, we describe its syntax, structure, +and usage. Our reference implementation is +.Xr mandoc 1 . +The +.Sx COMPATIBILITY +section describes compatibility with +.Xr groff 1 . +. +.Pp +An +.Nm +document follows simple rules: lines beginning with the control +character +.Sq \. +are parsed for macros. Other lines are interpreted within the scope of +prior macros: +.Bd -literal -offset indent +\&.Sh Macro lines change control state. +Other lines are interpreted within the current state. +.Ed +. +. +.Sh LANGUAGE SYNTAX +.Nm +documents may contain only graphable 7-bit ASCII characters, the space +character, and, in certain circumstances, the tab character. All +manuals must have +.Ux +line terminators. +. +. +.Ss Comments +Text following a +.Sq \e" , +whether in a macro or free-form text line, is ignored to the end of +line. A macro line with only a control character and comment escape, +.Sq \&.\e" , +is also ignored. Macro lines with only a control charater and optionally +whitespace are stripped from input. +. +. +.Ss Reserved Characters +Within a macro line, the following characters are reserved: +.Pp +.Bl -tag -width Ds -offset indent -compact +.It \&. +.Pq period +.It \&, +.Pq comma +.It \&: +.Pq colon +.It \&; +.Pq semicolon +.It \&( +.Pq left-parenthesis +.It \&) +.Pq right-parenthesis +.It \&[ +.Pq left-bracket +.It \&] +.Pq right-bracket +.It \&? +.Pq question +.It \&! +.Pq exclamation +.It \&| +.Pq vertical bar +.El +. +.Pp +Use of reserved characters is described in +.Sx MACRO SYNTAX . +For general use in macro lines, these characters must either be escaped +with a non-breaking space +.Pq Sq \e& +or, if applicable, an appropriate escape sequence used. +. +. +.Ss Special Characters +Special characters may occur in both macro and free-form lines. +Sequences begin with the escape character +.Sq \e +followed by either an open-parenthesis +.Sq \&( +for two-character sequences; an open-bracket +.Sq \&[ +for n-character sequences (terminated at a close-bracket +.Sq \&] ) ; +or a single one-character sequence. See +.Xr mandoc_char 7 +for a complete list. Examples include +.Sq \e(em +.Pq em-dash +and +.Sq \ee +.Pq back-slash . +. +. +.Ss Text Decoration +Terms may be text-decorated using the +.Sq \ef +escape followed by an indicator: B (bold), I, (italic), or P and R +(Roman, or reset). This form is not recommended for +.Nm , +which encourages semantic, not presentation, annotation. +. +. +.Ss Predefined Strings +Historically, +.Xr groff 1 +also defined a set of package-specific +.Dq predefined strings , +which, like +.Sx Special Characters , +demark special output characters and strings by way of input codes. +Predefined strings are escaped with the slash-asterisk, +.Sq \e* : +single-character +.Sq \e*X , +two-character +.Sq \e*(XX , +and N-character +.Sq \e*[N] . +See +.Xr mandoc_char 7 +for a complete list. Examples include +.Sq \e*(Am +.Pq ampersand +and +.Sq \e*(Ba +.Pq vertical bar . +. +. +.Ss Whitespace +In non-literal free-form lines, consecutive blocks of whitespace are +pruned from input and added later in the output filter, if applicable: +.Bd -literal -offset indent +These spaces are pruned from input. +\&.Bd \-literal +These are not. +\&.Ed +.Ed +. +.Pp +In macro lines, whitespace delimits arguments and is discarded. If +arguments are quoted, whitespace within the quotes is retained. +. +.Pp +Blank lines are only permitted within literal contexts, as are lines +containing only whitespace. Tab characters are only acceptable when +delimiting +.Sq \&Bl \-column +or when in a literal context. +. +. +.Ss Quotation +Macro arguments may be quoted with a double-quote to group +space-delimited terms or to retain blocks of whitespace. A quoted +argument begins with a double-quote preceded by whitespace. The next +double-quote not pair-wise adjacent to another double-quote terminates +the literal, regardless of surrounding whitespace. +. +.Pp +This produces tokens +.Sq a" , +.Sq b c , +.Sq de , +and +.Sq fg" . +Note that any quoted term, be it argument or macro, is indiscriminately +considered literal text. Thus, the following produces +.Sq \&Em a : +.Bd -literal -offset indent +\&.Em "Em a" +.Ed +. +.Pp +In free-form mode, quotes are regarded as opaque text. +. +.Ss Dates +There are several macros in +.Nm +that require a date argument. The +.Em canonical form +for dates is the American format: +.Pp +.D1 Cm Month Day , Year +.Pp +The +.Cm Day +value is an optionally zero-padded numeral. The +.Cm Month +value is the full month name. The +.Cm Year +value is the full four-digit year. +.Pp +The +.Em non-canonical form +is the same as the canonical form, but without the comma between the +.Cm Day +and +.Cm Year +field. +.Pp +Lastly, +.Em reduced form +dates range from only a +.Cm Year +to the full canonical or non-canonical form. +.Pp +Some examples of valid dates follow: +.Pp +.D1 "May, 2009" Pq reduced form +.D1 "2009" Pq reduced form +.D1 "May 20, 2009" Pq canonical form +.D1 "May 20 2009" Pq non-canonical form +. +.Ss Scaling Widths +Many macros support scaled widths for their arguments, such as +stipulating a two-inch list indentation with the following: +.Bd -literal -offset indent +\&.Bl -tag -width 2i +.Ed +. +.Pp +The syntax for scaled widths is +.Sq Li [+-]?[0-9]*.[0-9]*[:unit:] , +where a decimal must be preceded or proceeded by at least one digit. +Negative numbers, while accepted, are truncated to zero. The following +scaling units are accepted: +.Pp +.Bl -tag -width Ds -offset indent -compact +.It c +centimetre +.It i +inch +.It P +pica (~1/6 inch) +.It p +point (~1/72 inch) +.It f +synonym for +.Sq u +.It v +default vertical span +.It m +width of rendered +.Sq m +.Pq em +character +.It n +width of rendered +.Sq n +.Pq en +character +.It u +default horizontal span +.It M +mini-em (~1/100 em) +.El +.Pp +Using anything other than +.Sq m , +.Sq n , +.Sq u , +or +.Sq v +is necessarily non-portable across output media. See +.Sx COMPATIBILITY . +. +. +.Sh MANUAL STRUCTURE +A well-formed +.Nm +document consists of a document prologue followed by one or more +sections. +.Pp +The prologue, which consists of (in order) the +.Sx \&Dd , +.Sx \&Dt , +and +.Sx \&Os +macros, is required for every document. +.Pp +The first section (sections are denoted by +.Sx \&Sh ) +must be the NAME section, consisting of at least one +.Sx \&Nm +followed by +.Sx \&Nd . +.Pp +Following that, convention dictates specifying at least the SYNOPSIS and +DESCRIPTION sections, although this varies between manual sections. +.Pp +The following is a well-formed skeleton +.Nm +file: +.Bd -literal -offset indent +\&.Dd $\&Mdocdate$ +\&.Dt mdoc 7 +\&.Os +\&. +\&.Sh NAME +\&.Nm foo +\&.Nd a description goes here +\&.\e\*q The next is for sections 2 & 3 only. +\&.\e\*q .Sh LIBRARY +\&. +\&.Sh SYNOPSIS +\&.Nm foo +\&.Op Fl options +\&.Ar +\&. +\&.Sh DESCRIPTION +The +\&.Nm +utility processes files ... +\&.\e\*q .Sh IMPLEMENTATION NOTES +\&.\e\*q The next is for sections 1 & 8 only. +\&.\e\*q .Sh EXIT STATUS +\&.\e\*q The next is for sections 2, 3, & 9 only. +\&.\e\*q .Sh RETURN VALUES +\&.\e\*q The next is for sections 1, 6, 7, & 8 only. +\&.\e\*q .Sh ENVIRONMENT +\&.\e\*q .Sh FILES +\&.\e\*q .Sh EXAMPLES +\&.\e\*q The next is for sections 1, 4, 6, 7, & 8 only. +\&.\e\*q .Sh DIAGNOSTICS +\&.\e\*q The next is for sections 2, 3, & 9 only. +\&.\e\*q .Sh ERRORS +\&.\e\*q .Sh SEE ALSO +\&.\e\*q .Xr foobar 1 +\&.\e\*q .Sh STANDARDS +\&.\e\*q .Sh HISTORY +\&.\e\*q .Sh AUTHORS +\&.\e\*q .Sh CAVEATS +\&.\e\*q .Sh BUGS +\&.\e\*q .Sh SECURITY CONSIDERATIONS +.Ed +.Pp +The sections in a +.Nm +document are conventionally ordered as they appear above. Sections +should be composed as follows: +.Bl -tag -width Ds -offset Ds +.It NAME +Must contain at least one +.Sx \&Nm +followed by +.Sx \&Nd . +The name needs re-stating since one +.Nm +documents can be used for more than one utility or function, such as +.Xr grep 1 +also being referenced as +.Xr egrep 1 +and +.Xr fgrep 1 . +.It LIBRARY +.It SYNOPSIS +.It DESCRIPTION +.It IMPLEMENTATION NOTES +.It EXIT STATUS +.It RETURN VALUES +.It ENVIRONMENT +.It FILES +.It EXAMPLES +.It DIAGNOSTICS +.It ERRORS +.It SEE ALSO +.It STANDARDS +.It HISTORY +.It AUTHORS +.It CAVEATS +.It BUGS +.It SECURITY CONSIDERATIONS +.El +. +. +.Sh MACRO SYNTAX +Macros are one to three three characters in length and begin with a +control character , +.Sq \&. , +at the beginning of the line. An arbitrary amount of whitespace may +sit between the control character and the macro name. Thus, the +following are equivalent: +.Bd -literal -offset indent +\&.Pp +\&.\ \ \ \&Pp +.Ed +. +.Pp +The syntax of a macro depends on its classification. In this section, +.Sq \-arg +refers to macro arguments, which may be followed by zero or more +.Sq parm +parameters; +.Sq \&Yo +opens the scope of a macro; and if specified, +.Sq \&Yc +closes it out. +. +.Pp +The +.Em Callable +column indicates that the macro may be called subsequent to the initial +line-macro. If a macro is not callable, then its invocation after the +initial line macro is interpreted as opaque text, such that +.Sq \&.Fl \&Sh +produces +.Sq Fl \&Sh . +. +.Pp +The +.Em Parsable +column indicates whether the macro may be followed by further +(ostensibly callable) macros. If a macro is not parsable, subsequent +macro invocations on the line will be interpreted as opaque text. +. +.Pp +The +.Em Scope +column, if applicable, describes closure rules. +. +. +.Ss Block full-explicit +Multi-line scope closed by an explicit closing macro. All macros +contains bodies; only +.Sx \&Bf +contains a head. +.Bd -literal -offset indent +\&.Yo \(lB\-arg \(lBparm...\(rB\(rB \(lBhead...\(rB +\(lBbody...\(rB +\&.Yc +.Ed +. +.Pp +.Bl -column -compact -offset indent "MacroX" "CallableX" "ParsableX" "closed by XXX" +.It Em Macro Ta Em Callable Ta Em Parsable Ta Em Scope +.It Sx \&Bd Ta \&No Ta \&No Ta closed by Sx \&Ed +.It Sx \&Bf Ta \&No Ta \&No Ta closed by Sx \&Ef +.It Sx \&Bk Ta \&No Ta \&No Ta closed by Sx \&Ek +.It Sx \&Bl Ta \&No Ta \&No Ta closed by Sx \&El +.It Sx \&Ed Ta \&No Ta \&No Ta opened by Sx \&Bd +.It Sx \&Ef Ta \&No Ta \&No Ta opened by Sx \&Bf +.It Sx \&Ek Ta \&No Ta \&No Ta opened by Sx \&Bk +.It Sx \&El Ta \&No Ta \&No Ta opened by Sx \&Bl +.El +. +. +.Ss Block full-implicit +Multi-line scope closed by end-of-file or implicitly by another macro. +All macros have bodies; some +.Po +.Sx \&It Fl bullet , +.Fl hyphen , +.Fl dash , +.Fl enum , +.Fl item +.Pc +don't have heads; only one +.Po +.Sx \&It Fl column +.Pc +has multiple heads. +.Bd -literal -offset indent +\&.Yo \(lB\-arg \(lBparm...\(rB\(rB \(lBhead... \(lBTa head...\(rB\(rB +\(lBbody...\(rB +.Ed +. +.Pp +.Bl -column -compact -offset indent "MacroX" "CallableX" "ParsableX" "closed by XXXXXXXXXXX" +.It Em Macro Ta Em Callable Ta Em Parsable Ta Em Scope +.It Sx \&It Ta \&No Ta Yes Ta closed by Sx \&It , Sx \&El +.It Sx \&Nd Ta \&No Ta \&No Ta closed by Sx \&Sh +.It Sx \&Sh Ta \&No Ta \&No Ta closed by Sx \&Sh +.It Sx \&Ss Ta \&No Ta \&No Ta closed by Sx \&Sh , Sx \&Ss +.El +. +. +.Ss Block partial-explicit +Like block full-explicit, but also with single-line scope. Each +has at least a body and, in limited circumstances, a head +.Po +.Sx \&Fo , +.Sx \&Eo +.Pc +and/or tail +.Pq Sx \&Ec . +.Bd -literal -offset indent +\&.Yo \(lB\-arg \(lBparm...\(rB\(rB \(lBhead...\(rB +\(lBbody...\(rB +\&.Yc \(lBtail...\(rB + +\&.Yo \(lB\-arg \(lBparm...\(rB\(rB \(lBhead...\(rB \ +\(lBbody...\(rB \&Yc \(lBtail...\(rB +.Ed +. +.Pp +.Bl -column "MacroX" "CallableX" "ParsableX" "closed by XXXX" -compact -offset indent +.It Em Macro Ta Em Callable Ta Em Parsable Ta Em Scope +.It Sx \&Ac Ta Yes Ta Yes Ta opened by Sx \&Ao +.It Sx \&Ao Ta Yes Ta Yes Ta closed by Sx \&Ac +.It Sx \&Bc Ta Yes Ta Yes Ta closed by Sx \&Bo +.It Sx \&Bo Ta Yes Ta Yes Ta opened by Sx \&Bc +.It Sx \&Brc Ta Yes Ta Yes Ta opened by Sx \&Bro +.It Sx \&Bro Ta Yes Ta Yes Ta closed by Sx \&Brc +.It Sx \&Dc Ta Yes Ta Yes Ta opened by Sx \&Do +.It Sx \&Do Ta Yes Ta Yes Ta closed by Sx \&Dc +.It Sx \&Ec Ta Yes Ta Yes Ta opened by Sx \&Eo +.It Sx \&Eo Ta Yes Ta Yes Ta closed by Sx \&Ec +.It Sx \&Fc Ta Yes Ta Yes Ta opened by Sx \&Fo +.It Sx \&Fo Ta \&No Ta \&No Ta closed by Sx \&Fc +.It Sx \&Oc Ta Yes Ta Yes Ta closed by Sx \&Oo +.It Sx \&Oo Ta Yes Ta Yes Ta opened by Sx \&Oc +.It Sx \&Pc Ta Yes Ta Yes Ta closed by Sx \&Po +.It Sx \&Po Ta Yes Ta Yes Ta opened by Sx \&Pc +.It Sx \&Qc Ta Yes Ta Yes Ta opened by Sx \&Oo +.It Sx \&Qo Ta Yes Ta Yes Ta closed by Sx \&Oc +.It Sx \&Re Ta \&No Ta \&No Ta opened by Sx \&Rs +.It Sx \&Rs Ta \&No Ta \&No Ta closed by Sx \&Re +.It Sx \&Sc Ta Yes Ta Yes Ta opened by Sx \&So +.It Sx \&So Ta Yes Ta Yes Ta closed by Sx \&Sc +.It Sx \&Xc Ta Yes Ta Yes Ta opened by Sx \&Xo +.It Sx \&Xo Ta Yes Ta Yes Ta closed by Sx \&Xc +.El +. +. +.Ss Block partial-implicit +Like block full-implicit, but with single-line scope closed by +.Sx Reserved Characters +or end of line. +.Bd -literal -offset indent +\&.Yo \(lB\-arg \(lBval...\(rB\(rB \(lBbody...\(rB \(lBres...\(rB +.Ed +. +.Pp +.Bl -column "MacroX" "CallableX" "ParsableX" -compact -offset indent +.It Em Macro Ta Em Callable Ta Em Parsable +.It Sx \&Aq Ta Yes Ta Yes +.It Sx \&Bq Ta Yes Ta Yes +.It Sx \&Brq Ta Yes Ta Yes +.It Sx \&D1 Ta \&No Ta \&Yes +.It Sx \&Dl Ta \&No Ta Yes +.It Sx \&Dq Ta Yes Ta Yes +.It Sx \&Op Ta Yes Ta Yes +.It Sx \&Pq Ta Yes Ta Yes +.It Sx \&Ql Ta Yes Ta Yes +.It Sx \&Qq Ta Yes Ta Yes +.It Sx \&Sq Ta Yes Ta Yes +.El +. +. +.Ss In-line +Closed by +.Sx Reserved Characters , +end of line, fixed argument lengths, and/or subsequent macros. In-line +macros have only text children. If a number (or inequality) of +arguments is +.Pq n , +then the macro accepts an arbitrary number of arguments. +.Bd -literal -offset indent +\&.Yo \(lB\-arg \(lBval...\(rB\(rB \(lBargs...\(rB \(lbres...\(rb + +\&.Yo \(lB\-arg \(lBval...\(rB\(rB \(lBargs...\(rB Yc... + +\&.Yo \(lB\-arg \(lBval...\(rB\(rB arg0 arg1 argN +.Ed +. +.Pp +.Bl -column "MacroX" "CallableX" "ParsableX" "Arguments" -compact -offset indent +.It Em Macro Ta Em Callable Ta Em Parsable Ta Em Arguments +.It Sx \&%A Ta \&No Ta \&No Ta >0 +.It Sx \&%B Ta \&No Ta \&No Ta >0 +.It Sx \&%C Ta \&No Ta \&No Ta >0 +.It Sx \&%D Ta \&No Ta \&No Ta >0 +.It Sx \&%I Ta \&No Ta \&No Ta >0 +.It Sx \&%J Ta \&No Ta \&No Ta >0 +.It Sx \&%N Ta \&No Ta \&No Ta >0 +.It Sx \&%O Ta \&No Ta \&No Ta >0 +.It Sx \&%P Ta \&No Ta \&No Ta >0 +.It Sx \&%R Ta \&No Ta \&No Ta >0 +.It Sx \&%T Ta \&No Ta \&No Ta >0 +.It Sx \&%V Ta \&No Ta \&No Ta >0 +.It Sx \&Ad Ta Yes Ta Yes Ta n +.It Sx \&An Ta Yes Ta Yes Ta n +.It Sx \&Ap Ta Yes Ta Yes Ta 0 +.It Sx \&Ar Ta Yes Ta Yes Ta n +.It Sx \&At Ta Yes Ta Yes Ta 1 +.It Sx \&Bsx Ta Yes Ta Yes Ta n +.It Sx \&Bt Ta \&No Ta \&No Ta 0 +.It Sx \&Bx Ta Yes Ta Yes Ta n +.It Sx \&Cd Ta Yes Ta Yes Ta >0 +.It Sx \&Cm Ta Yes Ta Yes Ta n +.It Sx \&Db Ta \&No Ta \&No Ta 1 +.It Sx \&Dd Ta \&No Ta \&No Ta >0 +.It Sx \&Dt Ta \&No Ta \&No Ta n +.It Sx \&Dv Ta Yes Ta Yes Ta n +.It Sx \&Dx Ta Yes Ta Yes Ta n +.It Sx \&Em Ta Yes Ta Yes Ta >0 +.It Sx \&En Ta \&No Ta \&No Ta 0 +.It Sx \&Er Ta Yes Ta Yes Ta >0 +.It Sx \&Es Ta \&No Ta \&No Ta 0 +.It Sx \&Ev Ta Yes Ta Yes Ta n +.It Sx \&Ex Ta \&No Ta \&No Ta n +.It Sx \&Fa Ta Yes Ta Yes Ta n +.It Sx \&Fd Ta \&No Ta \&No Ta >0 +.It Sx \&Fl Ta Yes Ta Yes Ta n +.It Sx \&Fn Ta Yes Ta Yes Ta >0 +.It Sx \&Fr Ta \&No Ta \&No Ta n +.It Sx \&Ft Ta Yes Ta Yes Ta n +.It Sx \&Fx Ta Yes Ta Yes Ta n +.It Sx \&Hf Ta \&No Ta \&No Ta n +.It Sx \&Ic Ta Yes Ta Yes Ta >0 +.It Sx \&In Ta \&No Ta \&No Ta n +.It Sx \&Lb Ta \&No Ta \&No Ta 1 +.It Sx \&Li Ta Yes Ta Yes Ta n +.It Sx \&Lk Ta Yes Ta Yes Ta n +.It Sx \&Lp Ta \&No Ta \&No Ta 0 +.It Sx \&Ms Ta Yes Ta Yes Ta >0 +.It Sx \&Mt Ta Yes Ta Yes Ta >0 +.It Sx \&Nm Ta Yes Ta Yes Ta n +.It Sx \&No Ta Yes Ta Yes Ta 0 +.It Sx \&Ns Ta Yes Ta Yes Ta 0 +.It Sx \&Nx Ta Yes Ta Yes Ta n +.It Sx \&Os Ta \&No Ta \&No Ta n +.It Sx \&Ot Ta \&No Ta \&No Ta n +.It Sx \&Ox Ta Yes Ta Yes Ta n +.It Sx \&Pa Ta Yes Ta Yes Ta n +.It Sx \&Pf Ta \&No Ta Yes Ta 1 +.It Sx \&Pp Ta \&No Ta \&No Ta 0 +.It Sx \&Rv Ta \&No Ta \&No Ta n +.It Sx \&Sm Ta \&No Ta \&No Ta 1 +.It Sx \&St Ta \&No Ta Yes Ta 1 +.It Sx \&Sx Ta Yes Ta Yes Ta >0 +.It Sx \&Sy Ta Yes Ta Yes Ta >0 +.It Sx \&Tn Ta Yes Ta Yes Ta >0 +.It Sx \&Ud Ta \&No Ta \&No Ta 0 +.It Sx \&Ux Ta Yes Ta Yes Ta n +.It Sx \&Va Ta Yes Ta Yes Ta n +.It Sx \&Vt Ta Yes Ta Yes Ta >0 +.It Sx \&Xr Ta Yes Ta Yes Ta >0, <3 +.It Sx \&br Ta \&No Ta \&No Ta 0 +.It Sx \&sp Ta \&No Ta \&No Ta 1 +.El +. +. +.Sh REFERENCE +This section is a canonical reference of all macros, arranged +alphabetically. For the scoping of individual macros, see +.Sx MACRO SYNTAX . +. +.Ss \&%A +Author name of an +.Sx \&Rs +block. Multiple authors should each be accorded their own +.Sx \%%A +line. Author names should be ordered with full or abbreviated +forename(s) first, then full surname. +. +.Ss \&%B +Book title of an +.Sx \&Rs +block. This macro may also be used in a non-bibliographic context when +referring to book titles. +. +.Ss \&%C +Publication city or location of an +.Sx \&Rs +block. +.Pp +.Em Remarks : +this macro is not implemented in +.Xr groff 1 . +. +.Ss \&%D +Publication date of an +.Sx \&Rs +block. This should follow the reduced syntax for +.Sx Dates . +Canonical or non-canonical form is not necessary since publications are +often referenced only by year, or month and year. +. +.Ss \&%I +Publisher or issuer name of an +.Sx \&Rs +block. +. +.Ss \&%J +Journal name of an +.Sx \&Rs +block. +. +.Ss \&%N +Issue number (usually for journals) of an +.Sx \&Rs +block. +. +.Ss \&%O +Optional information of an +.Sx \&Rs +block. +. +.Ss \&%P +Book or journal page number of an +.Sx \&Rs +block. +. +.Ss \&%Q +Institutional author (school, government, etc.) of an +.Sx \&Rs +block. Multiple institutional authors should each be accorded their own +.Sx \&%Q +line. +. +.Ss \&%R +Technical report name of an +.Sx \&Rs +block. +. +.Ss \&%T +Article title of an +.Sx \&Rs +block. This macro may also be used in a non-bibliographical context +when referring to article titles. +. +.Ss \&%V +Volume number of an +.Sx \&Rs +block. +. +.Ss \&Ac +Closes an +.Sx \&Ao +block. Does not have any tail arguments. +. +.Ss \&Ad +Address construct: usually in the context of an computational address in +memory, not a physical (post) address. +.Pp +Examples: +.Bd -literal -offset indent +\&.Ad [0,$] +\&.Ad 0x00000000 +.Ed +. +.Ss \&An +Author name. This macro may alternatively accepts the following +arguments, although these may not be specified along with a parameter: +.Bl -tag -width 12n -offset indent +.It Fl split +Renders a line break before each author listing. +.It Fl nosplit +The opposite of +.Fl split . +.El +.Pp +In the AUTHORS section, the default is not to split the first author +listing, but all subsequent author listings, whether or not they're +interspersed by other macros or text, are split. Thus, specifying +.Fl split +will cause the first listing also to be split. If not in the AUTHORS +section, the default is not to split. +.Pp +Examples: +.Bd -literal -offset indent +\&.An -nosplit +\&.An J. E. Hopcraft , +\&.An J. D. Ullman . +.Ed +.Pp +.Em Remarks : +the effects of +.Fl split +or +.Fl nosplit +are re-set when entering the AUTHORS section, so if one specifies +.Sx \&An Fl nosplit +in the general document body, it must be re-specified in the AUTHORS +section. +. +.Ss \&Ao +Begins a block enclosed by angled brackets. Does not have any head +arguments. +.Pp +Examples: +.Bd -literal -offset indent +\&.Fl -key= Ns Ao Ar val Ac +.Ed +.Pp +See also +.Sx \&Aq . +. +.Ss \&Ap +Inserts an apostrophe without any surrounding white-space. This is +generally used as a grammatic device when referring to the verb form of +a function: +.Bd -literal -offset indent +\&.Fn execve Ap d +.Ed +. +.Ss \&Aq +Encloses its arguments in angled brackets. +.Pp +Examples: +.Bd -literal -offset indent +\&.Fl -key= Ns Aq Ar val +.Ed +.Pp +.Em Remarks : +this macro is often abused for rendering URIs, which should instead use +.Sx \&Lk +or +.Sx \&Mt , +or to note pre-processor +.Dq Li #include +statements, which should use +.Sx \&In . +.Pp +See also +.Sx \&Ao . +. +.Ss \&Ar +Command arguments. If an argument is not provided, the string +.Dq file ... +is used as a default. +.Pp +Examples: +.Bd -literal -offset indent +\&.Fl o Ns Ar file1 +\&.Ar +\&.Ar arg1 , arg2 . +.Ed +. +.Ss \&At +Formats an AT&T version. Accepts at most one parameter: +.Bl -tag -width 12n -offset indent +.It Cm v[1-7] | 32v +A version of +.At . +.It Cm V[.[1-4]]? +A system version of +.At . +.El +.Pp +Note that these parameters do not begin with a hyphen. +.Pp +Examples: +.Bd -literal -offset indent +\&.At +\&.At V.1 +.Ed +.Pp +See also +.Sx \&Bsx , +.Sx \&Bx , +.Sx \&Dx , +.Sx \&Fx , +.Sx \&Nx , +.Sx \&Ox , +and +.Sx \&Ux . +. +.Ss \&Bc +Closes a +.Sx \&Bo +block. Does not have any tail arguments. +. +.Ss \&Bd +Begins a display block. A display is collection of macros or text which +may be collectively offset or justified in a manner different from that +of the enclosing context. By default, the block is preceded by a +vertical space. +.Pp +Each display is associated with a type, which must be one of the +following arguments: +.Bl -tag -width 12n -offset indent +.It Fl ragged +Only left-justify the block. +.It Fl unfilled +Do not justify the block at all. +.It Fl filled +Left- and right-justify the block. +.It Fl literal +Alias for +.Fl unfilled . +.It Fl centered +Centre-justify each line. +.El +.Pp +The type must be provided first. Secondary arguments are as follows: +.Bl -tag -width 12n -offset indent +.It Fl offset Ar width +Offset by the value of +.Ar width , +which is interpreted as one of the following, specified in order: +.Bl -item +.It +As one of the pre-defined strings +.Ar indent , +the width of standard indentation; +.Ar indent-two , +twice +.Ar indent ; +.Ar left , +which has no effect ; +.Ar right , +which justifies to the right margin; and +.Ar center , +which aligns around an imagined centre axis. +.It +As a precalculated width for a named macro. The most popular is the +imaginary macro +.Ar Ds , +which resolves to +.Ar 6n . +.It +As a scaling unit following the syntax described in +.Sx Scaling Widths . +.It +As the calculated string length of the opaque string. +.El +.Pp +If unset, it will revert to the value of +.Ar 8n +as described in +.Sx Scaling Widths . +.It Fl compact +Do not assert a vertical space before the block. +.It Fl file Ar file +Prepend the file +.Ar file +before any text or macros within the block. +.El +.Pp +Examples: +.Bd -literal -offset indent +\&.Bd \-unfilled \-offset two-indent \-compact + Hello world. +\&.Ed +.Ed +.Pp +See also +.Sx \&D1 +and +.Sx \&Dl . +. +.Ss \&Bf +.Ss \&Bk +.Ss \&Bl +. +.Ss \&Bo +Begins a block enclosed by square brackets. Does not have any head +arguments. +.Pp +Examples: +.Bd -literal -offset indent +\&.Bo 1 , +\&.Dv BUFSIZ Bc +.Ed +.Pp +See also +.Sx \&Bq . +. +.Ss \&Bq +Encloses its arguments in square brackets. +.Pp +Examples: +.Bd -literal -offset indent +\&.Bq 1 , Dv BUFSIZ +.Ed +.Pp +.Em Remarks : +this macro is sometimes abused to emulate optional arguments for +commands; the correct macros to use for this purpose are +.Sx \&Op , +.Sx \&Oo , +and +.Sx \&Oc . +.Pp +See also +.Sx \&Bo . +. +.Ss \&Brc +Closes a +.Sx \&Bro +block. Does not have any tail arguments. +. +.Ss \&Bro +Begins a block enclosed by curly braces. Does not have any head +arguments. +.Pp +Examples: +.Bd -literal -offset indent +\&.Bro 1 , ... , +\&.Va n Brc +.Ed +.Pp +See also +.Sx \&Brq . +. +.Ss \&Brq +Encloses its arguments in curly braces. +.Pp +Examples: +.Bd -literal -offset indent +\&.Brq 1 , ... , Va n +.Ed +.Pp +See also +.Sx \&Bro . +. +.Ss \&Bsx +Format the BSD/OS version provided as an argument, or a default value if +no argument is provided. +.Pp +Examples: +.Bd -literal -offset indent +\&.Bsx 1.0 +\&.Bsx +.Ed +.Pp +See also +.Sx \&At , +.Sx \&Bx , +.Sx \&Dx , +.Sx \&Fx , +.Sx \&Nx , +.Sx \&Ox , +and +.Sx \&Ux . +. +.Ss \&Bt +Prints +.Dq is currently in beta test. +. +.Ss \&Bx +Format the BSD version provided as an argument, or a default value if no +argument is provided. +.Pp +Examples: +.Bd -literal -offset indent +\&.Bx 4.4 +\&.Bx +.Ed +.Pp +See also +.Sx \&At , +.Sx \&Bsx , +.Sx \&Dx , +.Sx \&Fx , +.Sx \&Nx , +.Sx \&Ox , +and +.Sx \&Ux . +. +.Ss \&Cd +Configuration declaration (suggested for use only in section four +manuals). This denotes strings accepted by +.Xr config 8 . +.Pp +Examples: +.Bd -literal -offset indent +\&.Cd device le0 at scode? +.Ed +.Pp +.Em Remarks : +this macro is commonly abused by using quoted literals to retain +white-space and align consecutive +.Sx \&Cd +declarations. This practise is discouraged. +. +.Ss \&Cm +Command modifiers. Useful when specifying configuration options or +keys. +.Pp +Examples: +.Bd -literal -offset indent +\&.Cm ControlPath +\&.Cm ControlMaster +.Ed +.Pp +See also +.Sx \&Fl . +. +.Ss \&D1 +One-line indented display. This is formatted by the default rules and +is useful for simple indented statements. It is followed by a newline. +.Pp +Examples: +.Bd -literal -offset indent +\&.D1 Fl abcdefgh +.Ed +.Pp +See also +.Sx \&Bd +and +.Sx \&Dl . +. +.Ss \&Db +.Ss \&Dc +Closes a +.Sx \&Do +block. Does not have any tail arguments. +. +.Ss \&Dd +Document date. This is the mandatory first macro of any +.Nm +manual. Its calling syntax is as follows: +.Pp +.D1 \. Ns Sx \&Dd Cm date +.Pp +The +.Cm date +field may be either +.Ar $\&Mdocdate$ , +which signifies the current manual revision date dictated by +.Xr cvs 1 +or instead a valid canonical date as specified by +.Sx Dates . +.Pp +Examples: +.Bd -literal -offset indent +\&.Dd $\&Mdocdate$ +\&.Dd $\&Mdocdate: July 21 2007$ +\&.Dd July 21, 2007 +.Ed +.Pp +See also +.Sx \&Dt +and +.Sx \&Os . +. +.Ss \&Dl +One-line intended display. This is formatted as literal text and is +useful for commands and invocations. It is followed by a newline. +.Pp +Examples: +.Bd -literal -offset indent +\&.Dl % mandoc mdoc.7 | less +.Ed +.Pp +See also +.Sx \&Bd +and +.Sx \&D1 . +. +.Ss \&Do +Begins a block enclosed by double quotes. Does not have any head +arguments. +.Pp +Examples: +.Bd -literal -offset indent +\&.D1 Do April is the cruellest month Dc \e(em T.S. Eliot +.Ed +.Pp +See also +.Sx \&Dq . +. +.Ss \&Dq +Encloses its arguments in double quotes. +.Pp +Examples: +.Bd -literal -offset indent +\&.Dq April is the cruellest month +\e(em T.S. Eliot +.Ed +.Pp +See also +.Sx \&Do . +. +.Ss \&Dt +Document title. This is the mandatory second macro of any +.Nm +file. Its calling syntax is as follows: +.Pp +.D1 \. Ns Sx \&Dt Cm title section Op Cm volume | arch +.Pp +Its arguments are as follows: +.Bl -tag -width Ds -offset Ds +.It Cm title +The document's title (name). This should be capitalised and is +required. +.It Cm section +The manual section. This may be one of +.Ar 1 +.Pq utilities , +.Ar 2 +.Pq system calls , +.Ar 3 +.Pq libraries , +.Ar 3p +.Pq Perl libraries , +.Ar 4 +.Pq devices , +.Ar 5 +.Pq file formats , +.Ar 6 +.Pq games , +.Ar 7 +.Pq miscellaneous , +.Ar 8 +.Pq system utilities , +.Ar 9 +.Pq kernel functions , +.Ar X11 +.Pq X Window System , +.Ar X11R6 +.Pq X Window System , +.Ar unass +.Pq unassociated , +.Ar local +.Pq local system , +.Ar draft +.Pq draft manual , +or +.Ar paper +.Pq paper . +It is also required and should correspond to the manual's filename +suffix. +.It Cm volume +This overrides the volume inferred from +.Ar section . +This field is optional, and if specified, must be one of +.Ar USD +.Pq users' supplementary documents , +.Ar PS1 +.Pq programmers' supplementary documents , +.Ar AMD +.Pq administrators' supplementary documents , +.Ar SMM +.Pq system managers' manuals , +.Ar URM +.Pq users' reference manuals , +.Ar PRM +.Pq programmers' reference manuals , +.Ar KM +.Pq kernel manuals , +.Ar IND +.Pq master index , +.Ar MMI +.Pq master index , +.Ar LOCAL +.Pq local manuals , +.Ar LOC +.Pq local manuals , +or +.Ar CON +.Pq contributed manuals . +.It Cm arch +This specifies a specific relevant architecture. If +.Cm volume +is not provided, it may be used in its place, else it may be used +subsequent that. It, too, is optional. It must be one of +.Ar alpha , +.Ar amd64 , +.Ar amiga , +.Ar arc , +.Ar arm , +.Ar armish , +.Ar aviion , +.Ar hp300 , +.Ar hppa , +.Ar hppa64 , +.Ar i386 , +.Ar landisk , +.Ar luna88k , +.Ar mac68k , +.Ar macppc , +.Ar mvme68k , +.Ar mvme88k , +.Ar mvmeppc , +.Ar pmax , +.Ar sgi , +.Ar socppc , +.Ar sparc , +.Ar sparc64 , +.Ar sun3 , +.Ar vax , +or +.Ar zaurus . +.El +.Pp +Examples: +.Bd -literal -offset indent +\&.Dt FOO 1 +\&.Dt FOO 4 KM +\&.Dt FOO 9 i386 +\&.Dt FOO 9 KM i386 +.Ed +.Pp +See also +.Sx \&Dd +and +.Sx \&Os . +. +.Ss \&Dv +Defined variables such as preprocessor constants. +.Pp +Examples: +.Bd -literal -offset indent +\&.Dv BUFSIZ +\&.Dv STDOUT_FILENO +.Ed +.Pp +See also +.Sx \&Er . +. +.Ss \&Dx +Format the DragonFlyBSD version provided as an argument, or a default +value if no argument is provided. +.Pp +Examples: +.Bd -literal -offset indent +\&.Dx 2.4.1 +\&.Dx +.Ed +.Pp +See also +.Sx \&At , +.Sx \&Bsx , +.Sx \&Bx , +.Sx \&Fx , +.Sx \&Nx , +.Sx \&Ox , +and +.Sx \&Ux . +. +.Ss \&Ec +.Ss \&Ed +.Ss \&Ef +.Ss \&Ek +.Ss \&El +.Ss \&Em +Denotes text that should be emphasised. Note that this is a +presentation term and should not be used for stylistically decorating +technical terms. +.Pp +Examples: +.Bd -literal -offset indent +\&.Ed Warnings! +\&.Ed Remarks : +.Ed +. +.Ss \&En +.Ss \&Eo +.Ss \&Er +Error constants (suggested for use only in section two manuals). +.Pp +Examples: +.Bd -literal -offset indent +\&.Er EPERM +\&.Er ENOENT +.Ed +.Pp +See also +.Sx \&Dv . +. +.Ss \&Es +. +.Ss \&Ev +Environmental variables such as those specified in +.Xr environ 7 . +.Pp +Examples: +.Bd -literal -offset indent +\&.Ev DISPLAY +\&.Ev PATH +.Ed +. +.Ss \&Ex +Inserts text regarding a utility's exit values. This macro must have +first the +.Fl std +argument specified, then an optional +.Ar utility . +If +.Ar utility +is not provided, the document's name as stipulated in +.Sx \&Nm +is provided. +.Ss \&Fa +.Ss \&Fc +.Ss \&Fd +.Ss \&Fl +.Ss \&Fn +.Ss \&Fo +.Ss \&Fr +.Ss \&Ft +.Ss \&Fx +Format the FreeBSD version provided as an argument, or a default value +if no argument is provided. +.Pp +Examples: +.Bd -literal -offset indent +\&.Fx 7.1 +\&.Fx +.Ed +.Pp +See also +.Sx \&At , +.Sx \&Bsx , +.Sx \&Bx , +.Sx \&Dx , +.Sx \&Nx , +.Sx \&Ox , +and +.Sx \&Ux . +. +.Ss \&Hf +.Ss \&Ic +.Ss \&In +.Ss \&It +.Ss \&Lb +.Ss \&Li +.Ss \&Lk +.Ss \&Lp +.Ss \&Ms +.Ss \&Mt +.Ss \&Nd +.Ss \&Nm +.Ss \&No +.Ss \&Ns +.Ss \&Nx +Format the NetBSD version provided as an argument, or a default value if +no argument is provided. +.Pp +Examples: +.Bd -literal -offset indent +\&.Nx 5.01 +\&.Nx +.Ed +.Pp +See also +.Sx \&At , +.Sx \&Bsx , +.Sx \&Bx , +.Sx \&Dx , +.Sx \&Fx , +.Sx \&Ox , +and +.Sx \&Ux . +. +.Ss \&Oc +.Ss \&Oo +.Ss \&Op +.Ss \&Os +Document operating system version. This is the mandatory third macro of +any +.Nm +file. Its calling syntax is as follows: +.Pp +.D1 \. Ns Sx \&Os Op Cm system +.Pp +The optional +.Cm system +parameter specifies the relevant operating system or environment. Left +unspecified, it defaults to the local operating system version. This is +the suggested form. +.Pp +Examples: +.Bd -literal -offset indent +\&.Os +\&.Os KTH/CSC/TCS +\&.Os BSD 4.3 +.Ed +.Pp +See also +.Sx \&Dd +and +.Sx \&Dt . +. +.Ss \&Ot +Unknown usage. +.Pp +.Em Remarks : +this macro has been deprecated. +. +.Ss \&Ox +Format the OpenBSD version provided as an argument, or a default value +if no argument is provided. +.Pp +Examples: +.Bd -literal -offset indent +\&.Ox 4.5 +\&.Ox +.Ed +.Pp +See also +.Sx \&At , +.Sx \&Bsx , +.Sx \&Bx , +.Sx \&Dx , +.Sx \&Fx , +.Sx \&Nx , +and +.Sx \&Ux . +. +.Ss \&Pa +.Ss \&Pc +.Ss \&Pf +.Ss \&Po +.Ss \&Pp +.Ss \&Pq +.Ss \&Qc +.Ss \&Ql +.Ss \&Qo +.Ss \&Qq +. +.Ss \&Re +Closes a +.Sx \&Rs +block. Does not have any tail arguments. +. +.Ss \&Rs +Begins a bibliographic +.Pq Dq reference +block. Does not have any head arguments. The block macro and may only +contain +.Sx \&%A , +.Sx \&%B , +.Sx \&%C , +.Sx \&%D , +.Sx \&%I , +.Sx \&%J , +.Sx \&%N , +.Sx \&%O , +.Sx \&%P , +.Sx \&%Q , +.Sx \&%R , +.Sx \&%T , +and +.Sx \&%V +child macros (at least one must be specified). +.Pp +Examples: +.Bd -literal -offset indent +\&.Rs +\&.%A J. E. Hopcroft +\&.%A J. D. Ullman +\&.%B Introduction to Automata Theory, Languages, and Computation +\&.%I Addison-Wesley +\&.%C Reading, Massachusettes +\&.%D 1979 +\&.Re +.Ed +.Pp +If an +.Sx \&Rs +block is used within a SEE ALSO section, a vertical space is asserted +before the rendered output, else the block continues on the current +line. +. +.Ss \&Rv +.Ss \&Sc +.Ss \&Sh +.Ss \&Sm +.Ss \&So +.Ss \&Sq +.Ss \&Ss +.Ss \&St +.Ss \&Sx +.Ss \&Sy +.Ss \&Tn +.Ss \&Ud +.Ss \&Ux +Format the UNIX name. Accepts no argument. +.Pp +Examples: +.Bd -literal -offset indent +\&.Ux +.Ed +.Pp +See also +.Sx \&At , +.Sx \&Bsx , +.Sx \&Bx , +.Sx \&Dx , +.Sx \&Fx , +.Sx \&Nx , +and +.Sx \&Ox . +. +.Ss \&Va +.Ss \&Vt +.Ss \&Xc +.Ss \&Xo +.Ss \&Xr +.Ss \&br +.Ss \&sp +. +. +.Sh COMPATIBILITY +This section documents compatibility with other roff implementations, at +this time limited to +.Xr groff 1 . +The term +.Qq historic groff +refers to those versions before the +.Pa doc.tmac +file re-write +.Pq somewhere between 1.15 and 1.19 . +. +.Pp +.Bl -dash -compact +.It +Negative scaling units are now truncated to zero instead of creating +interesting conditions, such as with +.Sq \&sp -1i . +Furthermore, the +.Sq f +scaling unit, while accepted, is rendered as the default unit. +.It +In quoted literals, groff allowed pair-wise double-quotes to produce a +standalone double-quote in formatted output. This idiosyncratic +behaviour is no longer applicable. +.It +Display types +.Sx \&Bd Fl center +and +.Fl right +are aliases for +.Fl left . +The +.Fl file Ar file +argument is ignored. Since text is not right-justified, +.Fl ragged +and +.Fl filled +are aliases, as are +.Fl literal +and +.Fl unfilled . +.It +Blocks of whitespace are stripped from both macro and free-form text +lines (except when in literal mode), while groff would retain whitespace +in free-form text lines. +.It +Historic groff has many un-callable macros. Most of these (excluding +some block-level macros) are now callable, conforming to the +non-historic groff version. +.It +The vertical bar +.Sq \(ba +made historic groff +.Qq go orbital +but is a proper delimiter in this implementation. +.It +.Sx \&It Fl nested +is assumed for all lists (it wasn't in historic groff): any list may be +nested and +.Fl enum +lists will restart the sequence only for the sub-list. +.It +Some manuals use +.Sx \&Li +incorrectly by following it with a reserved character and expecting the +delimiter to render. This is not supported. +.It +In groff, the +.Sx \&Fo +macro only produces the first parameter. This is no longer the case. +.El +. +. +.Sh SEE ALSO +.Xr mandoc 1 , +.Xr mandoc_char 7 +. +. +.Sh AUTHORS +The +.Nm +reference was written by +.An Kristaps Dzonsons Aq kristaps@kth.se . +.\" +.\" XXX: this really isn't the place for these caveats. +.\" . +.\" . +.\" .Sh CAVEATS +.\" There are many ambiguous parts of mdoc. +.\" . +.\" .Pp +.\" .Bl -dash -compact +.\" .It +.\" .Sq \&Fa +.\" should be +.\" .Sq \&Va +.\" as function arguments are variables. +.\" .It +.\" .Sq \&Ft +.\" should be +.\" .Sq \&Vt +.\" as function return types are still types. Furthermore, the +.\" .Sq \&Ft +.\" should be removed and +.\" .Sq \&Fo , +.\" which ostensibly follows it, should follow the same convention as +.\" .Sq \&Va . +.\" .It +.\" .Sq \&Va +.\" should formalise that only one or two arguments are acceptable: a +.\" variable name and optional, preceding type. +.\" .It +.\" .Sq \&Fd +.\" is ambiguous. It's commonly used to indicate an include file in the +.\" synopsis section. +.\" .Sq \&In +.\" should be used, instead. +.\" .It +.\" Only the +.\" .Sq \-literal +.\" argument to +.\" .Sq \&Bd +.\" makes sense. The remaining ones should be removed. +.\" .It +.\" The +.\" .Sq \&Xo +.\" and +.\" .Sq \&Xc +.\" macros should be deprecated. +.\" .It +.\" The +.\" .Sq \&Dt +.\" macro lacks clarity. It should be absolutely clear which title will +.\" render when formatting the manual page. +.\" .It +.\" A +.\" .Sq \&Lx +.\" should be provided for Linux (\(`a la +.\" .Sq \&Ox , +.\" .Sq \&Nx +.\" etc.). +.\" .It +.\" There's no way to refer to references in +.\" .Sq \&Rs/Re +.\" blocks. +.\" .It +.\" The \-split and \-nosplit dictates via +.\" .Sq \&An +.\" are re-set when entering and leaving the AUTHORS section. +.\" .El +.\" . diff --git a/usr.bin/mandoc/mdoc.c b/usr.bin/mandoc/mdoc.c new file mode 100644 index 0000000000..d7938435ee --- /dev/null +++ b/usr.bin/mandoc/mdoc.c @@ -0,0 +1,749 @@ +/* $Id: mdoc.c,v 1.30 2009/10/21 19:13:50 schwarze Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include + +#include +#include +#include +#include +#include +#include + +#include "libmdoc.h" + +const char *const __mdoc_merrnames[MERRMAX] = { + "trailing whitespace", /* ETAILWS */ + "unexpected quoted parameter", /* EQUOTPARM */ + "unterminated quoted parameter", /* EQUOTTERM */ + "system: malloc error", /* EMALLOC */ + "argument parameter suggested", /* EARGVAL */ + "macro disallowed in prologue", /* EBODYPROL */ + "macro disallowed in body", /* EPROLBODY */ + "text disallowed in prologue", /* ETEXTPROL */ + "blank line disallowed", /* ENOBLANK */ + "text parameter too long", /* ETOOLONG */ + "invalid escape sequence", /* EESCAPE */ + "invalid character", /* EPRINT */ + "document has no body", /* ENODAT */ + "document has no prologue", /* ENOPROLOGUE */ + "expected line arguments", /* ELINE */ + "invalid AT&T argument", /* EATT */ + "default name not yet set", /* ENAME */ + "missing list type", /* ELISTTYPE */ + "missing display type", /* EDISPTYPE */ + "too many display types", /* EMULTIDISP */ + "too many list types", /* EMULTILIST */ + "NAME section must be first", /* ESECNAME */ + "badly-formed NAME section", /* ENAMESECINC */ + "argument repeated", /* EARGREP */ + "expected boolean parameter", /* EBOOL */ + "inconsistent column syntax", /* ECOLMIS */ + "nested display invalid", /* ENESTDISP */ + "width argument missing", /* EMISSWIDTH */ + "invalid section for this manual section", /* EWRONGMSEC */ + "section out of conventional order", /* ESECOOO */ + "section repeated", /* ESECREP */ + "invalid standard argument", /* EBADSTAND */ + "multi-line arguments discouraged", /* ENOMULTILINE */ + "multi-line arguments suggested", /* EMULTILINE */ + "line arguments discouraged", /* ENOLINE */ + "prologue macro out of conventional order", /* EPROLOOO */ + "prologue macro repeated", /* EPROLREP */ + "invalid manual section", /* EBADMSEC */ + "invalid section", /* EBADSEC */ + "invalid font mode", /* EFONT */ + "invalid date syntax", /* EBADDATE */ + "invalid number format", /* ENUMFMT */ + "superfluous width argument", /* ENOWIDTH */ + "system: utsname error", /* EUTSNAME */ + "obsolete macro", /* EOBS */ + "end-of-line scope violation", /* EIMPBRK */ + "empty macro ignored", /* EIGNE */ + "unclosed explicit scope", /* EOPEN */ + "unterminated quoted phrase", /* EQUOTPHR */ + "closure macro without prior context", /* ENOCTX */ + "no description found for library", /* ELIB */ + "bad child for parent context", /* EBADCHILD */ + "list arguments preceding type", /* ENOTYPE */ +}; + +const char *const __mdoc_macronames[MDOC_MAX] = { + "Ap", "Dd", "Dt", "Os", + "Sh", "Ss", "Pp", "D1", + "Dl", "Bd", "Ed", "Bl", + "El", "It", "Ad", "An", + "Ar", "Cd", "Cm", "Dv", + "Er", "Ev", "Ex", "Fa", + "Fd", "Fl", "Fn", "Ft", + "Ic", "In", "Li", "Nd", + "Nm", "Op", "Ot", "Pa", + "Rv", "St", "Va", "Vt", + /* LINTED */ + "Xr", "\%A", "\%B", "\%D", + /* LINTED */ + "\%I", "\%J", "\%N", "\%O", + /* LINTED */ + "\%P", "\%R", "\%T", "\%V", + "Ac", "Ao", "Aq", "At", + "Bc", "Bf", "Bo", "Bq", + "Bsx", "Bx", "Db", "Dc", + "Do", "Dq", "Ec", "Ef", + "Em", "Eo", "Fx", "Ms", + "No", "Ns", "Nx", "Ox", + "Pc", "Pf", "Po", "Pq", + "Qc", "Ql", "Qo", "Qq", + "Re", "Rs", "Sc", "So", + "Sq", "Sm", "Sx", "Sy", + "Tn", "Ux", "Xc", "Xo", + "Fo", "Fc", "Oo", "Oc", + "Bk", "Ek", "Bt", "Hf", + "Fr", "Ud", "Lb", "Lp", + "Lk", "Mt", "Brq", "Bro", + /* LINTED */ + "Brc", "\%C", "Es", "En", + /* LINTED */ + "Dx", "\%Q", "br", "sp" + }; + +const char *const __mdoc_argnames[MDOC_ARG_MAX] = { + "split", "nosplit", "ragged", + "unfilled", "literal", "file", + "offset", "bullet", "dash", + "hyphen", "item", "enum", + "tag", "diag", "hang", + "ohang", "inset", "column", + "width", "compact", "std", + "filled", "words", "emphasis", + "symbolic", "nested", "centered" + }; + +const char * const *mdoc_macronames = __mdoc_macronames; +const char * const *mdoc_argnames = __mdoc_argnames; + +static void mdoc_free1(struct mdoc *); +static int mdoc_alloc1(struct mdoc *); +static struct mdoc_node *node_alloc(struct mdoc *, int, int, + int, enum mdoc_type); +static int node_append(struct mdoc *, + struct mdoc_node *); +static int parsetext(struct mdoc *, int, char *); +static int parsemacro(struct mdoc *, int, char *); +static int macrowarn(struct mdoc *, int, const char *); +static int pstring(struct mdoc *, int, int, + const char *, size_t); + + +const struct mdoc_node * +mdoc_node(const struct mdoc *m) +{ + + return(MDOC_HALT & m->flags ? NULL : m->first); +} + + +const struct mdoc_meta * +mdoc_meta(const struct mdoc *m) +{ + + return(MDOC_HALT & m->flags ? NULL : &m->meta); +} + + +/* + * Frees volatile resources (parse tree, meta-data, fields). + */ +static void +mdoc_free1(struct mdoc *mdoc) +{ + + if (mdoc->first) + mdoc_node_freelist(mdoc->first); + if (mdoc->meta.title) + free(mdoc->meta.title); + if (mdoc->meta.os) + free(mdoc->meta.os); + if (mdoc->meta.name) + free(mdoc->meta.name); + if (mdoc->meta.arch) + free(mdoc->meta.arch); + if (mdoc->meta.vol) + free(mdoc->meta.vol); +} + + +/* + * Allocate all volatile resources (parse tree, meta-data, fields). + */ +static int +mdoc_alloc1(struct mdoc *mdoc) +{ + + bzero(&mdoc->meta, sizeof(struct mdoc_meta)); + mdoc->flags = 0; + mdoc->lastnamed = mdoc->lastsec = SEC_NONE; + mdoc->last = calloc(1, sizeof(struct mdoc_node)); + if (NULL == mdoc->last) + return(0); + + mdoc->first = mdoc->last; + mdoc->last->type = MDOC_ROOT; + mdoc->next = MDOC_NEXT_CHILD; + return(1); +} + + +/* + * Free up volatile resources (see mdoc_free1()) then re-initialises the + * data with mdoc_alloc1(). After invocation, parse data has been reset + * and the parser is ready for re-invocation on a new tree; however, + * cross-parse non-volatile data is kept intact. + */ +int +mdoc_reset(struct mdoc *mdoc) +{ + + mdoc_free1(mdoc); + return(mdoc_alloc1(mdoc)); +} + + +/* + * Completely free up all volatile and non-volatile parse resources. + * After invocation, the pointer is no longer usable. + */ +void +mdoc_free(struct mdoc *mdoc) +{ + + mdoc_free1(mdoc); + free(mdoc); +} + + +/* + * Allocate volatile and non-volatile parse resources. + */ +struct mdoc * +mdoc_alloc(void *data, int pflags, const struct mdoc_cb *cb) +{ + struct mdoc *p; + + if (NULL == (p = calloc(1, sizeof(struct mdoc)))) + return(NULL); + if (cb) + (void)memcpy(&p->cb, cb, sizeof(struct mdoc_cb)); + + mdoc_hash_init(); + + p->data = data; + p->pflags = pflags; + + if (mdoc_alloc1(p)) + return(p); + + free(p); + return(NULL); +} + + +/* + * Climb back up the parse tree, validating open scopes. Mostly calls + * through to macro_end() in macro.c. + */ +int +mdoc_endparse(struct mdoc *m) +{ + + if (MDOC_HALT & m->flags) + return(0); + else if (mdoc_macroend(m)) + return(1); + m->flags |= MDOC_HALT; + return(0); +} + + +/* + * Main parse routine. Parses a single line -- really just hands off to + * the macro (parsemacro()) or text parser (parsetext()). + */ +int +mdoc_parseln(struct mdoc *m, int ln, char *buf) +{ + + if (MDOC_HALT & m->flags) + return(0); + + return('.' == *buf ? parsemacro(m, ln, buf) : + parsetext(m, ln, buf)); +} + + +int +mdoc_verr(struct mdoc *mdoc, int ln, int pos, + const char *fmt, ...) +{ + char buf[256]; + va_list ap; + + if (NULL == mdoc->cb.mdoc_err) + return(0); + + va_start(ap, fmt); + (void)vsnprintf(buf, sizeof(buf) - 1, fmt, ap); + va_end(ap); + + return((*mdoc->cb.mdoc_err)(mdoc->data, ln, pos, buf)); +} + + +int +mdoc_vwarn(struct mdoc *mdoc, int ln, int pos, const char *fmt, ...) +{ + char buf[256]; + va_list ap; + + if (NULL == mdoc->cb.mdoc_warn) + return(0); + + va_start(ap, fmt); + (void)vsnprintf(buf, sizeof(buf) - 1, fmt, ap); + va_end(ap); + + return((*mdoc->cb.mdoc_warn)(mdoc->data, ln, pos, buf)); +} + + +int +mdoc_err(struct mdoc *m, int line, int pos, int iserr, enum merr type) +{ + const char *p; + + p = __mdoc_merrnames[(int)type]; + assert(p); + + if (iserr) + return(mdoc_verr(m, line, pos, p)); + + return(mdoc_vwarn(m, line, pos, p)); +} + + +int +mdoc_macro(struct mdoc *m, int tok, + int ln, int pp, int *pos, char *buf) +{ + /* + * If we're in the prologue, deny "body" macros. Similarly, if + * we're in the body, deny prologue calls. + */ + if (MDOC_PROLOGUE & mdoc_macros[tok].flags && + MDOC_PBODY & m->flags) + return(mdoc_perr(m, ln, pp, EPROLBODY)); + if ( ! (MDOC_PROLOGUE & mdoc_macros[tok].flags) && + ! (MDOC_PBODY & m->flags)) + return(mdoc_perr(m, ln, pp, EBODYPROL)); + + return((*mdoc_macros[tok].fp)(m, tok, ln, pp, pos, buf)); +} + + +static int +node_append(struct mdoc *mdoc, struct mdoc_node *p) +{ + + assert(mdoc->last); + assert(mdoc->first); + assert(MDOC_ROOT != p->type); + + switch (mdoc->next) { + case (MDOC_NEXT_SIBLING): + mdoc->last->next = p; + p->prev = mdoc->last; + p->parent = mdoc->last->parent; + break; + case (MDOC_NEXT_CHILD): + mdoc->last->child = p; + p->parent = mdoc->last; + break; + default: + abort(); + /* NOTREACHED */ + } + + p->parent->nchild++; + + if ( ! mdoc_valid_pre(mdoc, p)) + return(0); + if ( ! mdoc_action_pre(mdoc, p)) + return(0); + + switch (p->type) { + case (MDOC_HEAD): + assert(MDOC_BLOCK == p->parent->type); + p->parent->head = p; + break; + case (MDOC_TAIL): + assert(MDOC_BLOCK == p->parent->type); + p->parent->tail = p; + break; + case (MDOC_BODY): + assert(MDOC_BLOCK == p->parent->type); + p->parent->body = p; + break; + default: + break; + } + + mdoc->last = p; + + switch (p->type) { + case (MDOC_TEXT): + if ( ! mdoc_valid_post(mdoc)) + return(0); + if ( ! mdoc_action_post(mdoc)) + return(0); + break; + default: + break; + } + + return(1); +} + + +static struct mdoc_node * +node_alloc(struct mdoc *m, int line, + int pos, int tok, enum mdoc_type type) +{ + struct mdoc_node *p; + + if (NULL == (p = calloc(1, sizeof(struct mdoc_node)))) { + (void)mdoc_nerr(m, m->last, EMALLOC); + return(NULL); + } + + p->sec = m->lastsec; + p->line = line; + p->pos = pos; + p->tok = tok; + if (MDOC_TEXT != (p->type = type)) + assert(p->tok >= 0); + + return(p); +} + + +int +mdoc_tail_alloc(struct mdoc *m, int line, int pos, int tok) +{ + struct mdoc_node *p; + + p = node_alloc(m, line, pos, tok, MDOC_TAIL); + if (NULL == p) + return(0); + if ( ! node_append(m, p)) + return(0); + m->next = MDOC_NEXT_CHILD; + return(1); +} + + +int +mdoc_head_alloc(struct mdoc *m, int line, int pos, int tok) +{ + struct mdoc_node *p; + + assert(m->first); + assert(m->last); + + p = node_alloc(m, line, pos, tok, MDOC_HEAD); + if (NULL == p) + return(0); + if ( ! node_append(m, p)) + return(0); + m->next = MDOC_NEXT_CHILD; + return(1); +} + + +int +mdoc_body_alloc(struct mdoc *m, int line, int pos, int tok) +{ + struct mdoc_node *p; + + p = node_alloc(m, line, pos, tok, MDOC_BODY); + if (NULL == p) + return(0); + if ( ! node_append(m, p)) + return(0); + m->next = MDOC_NEXT_CHILD; + return(1); +} + + +int +mdoc_block_alloc(struct mdoc *m, int line, int pos, + int tok, struct mdoc_arg *args) +{ + struct mdoc_node *p; + + p = node_alloc(m, line, pos, tok, MDOC_BLOCK); + if (NULL == p) + return(0); + p->args = args; + if (p->args) + (args->refcnt)++; + if ( ! node_append(m, p)) + return(0); + m->next = MDOC_NEXT_CHILD; + return(1); +} + + +int +mdoc_elem_alloc(struct mdoc *m, int line, int pos, + int tok, struct mdoc_arg *args) +{ + struct mdoc_node *p; + + p = node_alloc(m, line, pos, tok, MDOC_ELEM); + if (NULL == p) + return(0); + p->args = args; + if (p->args) + (args->refcnt)++; + if ( ! node_append(m, p)) + return(0); + m->next = MDOC_NEXT_CHILD; + return(1); +} + + +static int +pstring(struct mdoc *m, int line, int pos, const char *p, size_t len) +{ + struct mdoc_node *n; + size_t sv; + + n = node_alloc(m, line, pos, -1, MDOC_TEXT); + if (NULL == n) + return(mdoc_nerr(m, m->last, EMALLOC)); + + n->string = malloc(len + 1); + if (NULL == n->string) { + free(n); + return(mdoc_nerr(m, m->last, EMALLOC)); + } + + sv = strlcpy(n->string, p, len + 1); + + /* Prohibit truncation. */ + assert(sv < len + 1); + + if ( ! node_append(m, n)) + return(0); + m->next = MDOC_NEXT_SIBLING; + return(1); +} + + +int +mdoc_word_alloc(struct mdoc *m, int line, int pos, const char *p) +{ + + return(pstring(m, line, pos, p, strlen(p))); +} + + +void +mdoc_node_free(struct mdoc_node *p) +{ + + if (p->parent) + p->parent->nchild--; + if (p->string) + free(p->string); + if (p->args) + mdoc_argv_free(p->args); + free(p); +} + + +void +mdoc_node_freelist(struct mdoc_node *p) +{ + + if (p->child) + mdoc_node_freelist(p->child); + if (p->next) + mdoc_node_freelist(p->next); + + assert(0 == p->nchild); + mdoc_node_free(p); +} + + +/* + * Parse free-form text, that is, a line that does not begin with the + * control character. + */ +static int +parsetext(struct mdoc *m, int line, char *buf) +{ + int i, j; + + if (SEC_NONE == m->lastnamed) + return(mdoc_perr(m, line, 0, ETEXTPROL)); + + /* + * If in literal mode, then pass the buffer directly to the + * back-end, as it should be preserved as a single term. + */ + + if (MDOC_LITERAL & m->flags) + return(mdoc_word_alloc(m, line, 0, buf)); + + /* Disallow blank/white-space lines in non-literal mode. */ + + for (i = 0; ' ' == buf[i]; i++) + /* Skip leading whitespace. */ ; + if (0 == buf[i]) + return(mdoc_perr(m, line, 0, ENOBLANK)); + + /* + * Break apart a free-form line into tokens. Spaces are + * stripped out of the input. + */ + + for (j = i; buf[i]; i++) { + if (' ' != buf[i]) + continue; + + /* Escaped whitespace. */ + if (i && ' ' == buf[i] && '\\' == buf[i - 1]) + continue; + + buf[i++] = 0; + if ( ! pstring(m, line, j, &buf[j], (size_t)(i - j))) + return(0); + + for ( ; ' ' == buf[i]; i++) + /* Skip trailing whitespace. */ ; + + j = i; + if (0 == buf[i]) + break; + } + + if (j != i && ! pstring(m, line, j, &buf[j], (size_t)(i - j))) + return(0); + + m->next = MDOC_NEXT_SIBLING; + return(1); +} + + + + +static int +macrowarn(struct mdoc *m, int ln, const char *buf) +{ + if ( ! (MDOC_IGN_MACRO & m->pflags)) + return(mdoc_verr(m, ln, 0, + "unknown macro: %s%s", + buf, strlen(buf) > 3 ? "..." : "")); + return(mdoc_vwarn(m, ln, 0, "unknown macro: %s%s", + buf, strlen(buf) > 3 ? "..." : "")); +} + + +/* + * Parse a macro line, that is, a line beginning with the control + * character. + */ +int +parsemacro(struct mdoc *m, int ln, char *buf) +{ + int i, j, c; + char mac[5]; + + /* Empty lines are ignored. */ + + if (0 == buf[1]) + return(1); + + i = 1; + + /* Accept whitespace after the initial control char. */ + + if (' ' == buf[i]) { + i++; + while (buf[i] && ' ' == buf[i]) + i++; + if (0 == buf[i]) + return(1); + } + + /* Copy the first word into a nil-terminated buffer. */ + + for (j = 0; j < 4; j++, i++) { + if (0 == (mac[j] = buf[i])) + break; + else if (' ' == buf[i]) + break; + + /* Check for invalid characters. */ + + if (isgraph((u_char)buf[i])) + continue; + return(mdoc_perr(m, ln, i, EPRINT)); + } + + mac[j] = 0; + + if (j == 4 || j < 2) { + if ( ! macrowarn(m, ln, mac)) + goto err; + return(1); + } + + if (MDOC_MAX == (c = mdoc_hash_find(mac))) { + if ( ! macrowarn(m, ln, mac)) + goto err; + return(1); + } + + /* The macro is sane. Jump to the next word. */ + + while (buf[i] && ' ' == buf[i]) + i++; + + /* + * Begin recursive parse sequence. Since we're at the start of + * the line, we don't need to do callable/parseable checks. + */ + if ( ! mdoc_macro(m, c, ln, 1, &i, buf)) + goto err; + + return(1); + +err: /* Error out. */ + + m->flags |= MDOC_HALT; + return(0); +} diff --git a/usr.bin/mandoc/mdoc.h b/usr.bin/mandoc/mdoc.h new file mode 100644 index 0000000000..f92cfd7630 --- /dev/null +++ b/usr.bin/mandoc/mdoc.h @@ -0,0 +1,304 @@ +/* $Id: mdoc.h,v 1.14 2009/10/21 19:13:50 schwarze Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef MDOC_H +#define MDOC_H + +#include + +/* + * This library implements a validating scanner/parser for ``mdoc'' roff + * macro documents, a.k.a. BSD manual page documents. The mdoc.c file + * drives the parser, while macro.c describes the macro ontologies. + * validate.c pre- and post-validates parsed macros, and action.c + * performs actions on parsed and validated macros. + */ + +/* What follows is a list of ALL possible macros. */ + +#define MDOC_Ap 0 +#define MDOC_Dd 1 +#define MDOC_Dt 2 +#define MDOC_Os 3 +#define MDOC_Sh 4 +#define MDOC_Ss 5 +#define MDOC_Pp 6 +#define MDOC_D1 7 +#define MDOC_Dl 8 +#define MDOC_Bd 9 +#define MDOC_Ed 10 +#define MDOC_Bl 11 +#define MDOC_El 12 +#define MDOC_It 13 +#define MDOC_Ad 14 +#define MDOC_An 15 +#define MDOC_Ar 16 +#define MDOC_Cd 17 +#define MDOC_Cm 18 +#define MDOC_Dv 19 +#define MDOC_Er 20 +#define MDOC_Ev 21 +#define MDOC_Ex 22 +#define MDOC_Fa 23 +#define MDOC_Fd 24 +#define MDOC_Fl 25 +#define MDOC_Fn 26 +#define MDOC_Ft 27 +#define MDOC_Ic 28 +#define MDOC_In 29 +#define MDOC_Li 30 +#define MDOC_Nd 31 +#define MDOC_Nm 32 +#define MDOC_Op 33 +#define MDOC_Ot 34 +#define MDOC_Pa 35 +#define MDOC_Rv 36 +#define MDOC_St 37 +#define MDOC_Va 38 +#define MDOC_Vt 39 +#define MDOC_Xr 40 +#define MDOC__A 41 +#define MDOC__B 42 +#define MDOC__D 43 +#define MDOC__I 44 +#define MDOC__J 45 +#define MDOC__N 46 +#define MDOC__O 47 +#define MDOC__P 48 +#define MDOC__R 49 +#define MDOC__T 50 +#define MDOC__V 51 +#define MDOC_Ac 52 +#define MDOC_Ao 53 +#define MDOC_Aq 54 +#define MDOC_At 55 +#define MDOC_Bc 56 +#define MDOC_Bf 57 +#define MDOC_Bo 58 +#define MDOC_Bq 59 +#define MDOC_Bsx 60 +#define MDOC_Bx 61 +#define MDOC_Db 62 +#define MDOC_Dc 63 +#define MDOC_Do 64 +#define MDOC_Dq 65 +#define MDOC_Ec 66 +#define MDOC_Ef 67 +#define MDOC_Em 68 +#define MDOC_Eo 69 +#define MDOC_Fx 70 +#define MDOC_Ms 71 +#define MDOC_No 72 +#define MDOC_Ns 73 +#define MDOC_Nx 74 +#define MDOC_Ox 75 +#define MDOC_Pc 76 +#define MDOC_Pf 77 +#define MDOC_Po 78 +#define MDOC_Pq 79 +#define MDOC_Qc 80 +#define MDOC_Ql 81 +#define MDOC_Qo 82 +#define MDOC_Qq 83 +#define MDOC_Re 84 +#define MDOC_Rs 85 +#define MDOC_Sc 86 +#define MDOC_So 87 +#define MDOC_Sq 88 +#define MDOC_Sm 89 +#define MDOC_Sx 90 +#define MDOC_Sy 91 +#define MDOC_Tn 92 +#define MDOC_Ux 93 +#define MDOC_Xc 94 +#define MDOC_Xo 95 +#define MDOC_Fo 96 +#define MDOC_Fc 97 +#define MDOC_Oo 98 +#define MDOC_Oc 99 +#define MDOC_Bk 100 +#define MDOC_Ek 101 +#define MDOC_Bt 102 +#define MDOC_Hf 103 +#define MDOC_Fr 104 +#define MDOC_Ud 105 +#define MDOC_Lb 106 +#define MDOC_Lp 107 +#define MDOC_Lk 108 +#define MDOC_Mt 109 +#define MDOC_Brq 110 +#define MDOC_Bro 111 +#define MDOC_Brc 112 +#define MDOC__C 113 +#define MDOC_Es 114 +#define MDOC_En 115 +#define MDOC_Dx 116 +#define MDOC__Q 117 +#define MDOC_br 118 +#define MDOC_sp 119 +#define MDOC_MAX 120 + +/* What follows is a list of ALL possible macro arguments. */ + +#define MDOC_Split 0 +#define MDOC_Nosplit 1 +#define MDOC_Ragged 2 +#define MDOC_Unfilled 3 +#define MDOC_Literal 4 +#define MDOC_File 5 +#define MDOC_Offset 6 +#define MDOC_Bullet 7 +#define MDOC_Dash 8 +#define MDOC_Hyphen 9 +#define MDOC_Item 10 +#define MDOC_Enum 11 +#define MDOC_Tag 12 +#define MDOC_Diag 13 +#define MDOC_Hang 14 +#define MDOC_Ohang 15 +#define MDOC_Inset 16 +#define MDOC_Column 17 +#define MDOC_Width 18 +#define MDOC_Compact 19 +#define MDOC_Std 20 +#define MDOC_Filled 21 +#define MDOC_Words 22 +#define MDOC_Emphasis 23 +#define MDOC_Symbolic 24 +#define MDOC_Nested 25 +#define MDOC_Centred 26 +#define MDOC_ARG_MAX 27 + +/* Type of a syntax node. */ +enum mdoc_type { + MDOC_TEXT, + MDOC_ELEM, + MDOC_HEAD, + MDOC_TAIL, + MDOC_BODY, + MDOC_BLOCK, + MDOC_ROOT +}; + +/* Section (named/unnamed) of `Sh'. */ +enum mdoc_sec { + SEC_NONE, /* No section, yet. */ + SEC_NAME, + SEC_LIBRARY, + SEC_SYNOPSIS, + SEC_DESCRIPTION, + SEC_IMPLEMENTATION, + SEC_EXIT_STATUS, + SEC_RETURN_VALUES, + SEC_ENVIRONMENT, + SEC_FILES, + SEC_EXAMPLES, + SEC_DIAGNOSTICS, + SEC_COMPATIBILITY, + SEC_ERRORS, + SEC_SEE_ALSO, + SEC_STANDARDS, + SEC_HISTORY, + SEC_AUTHORS, + SEC_CAVEATS, + SEC_BUGS, + SEC_SECURITY, + SEC_CUSTOM /* User-defined. */ +}; + +/* Information from prologue. */ +struct mdoc_meta { + int msec; + char *vol; + char *arch; + time_t date; + char *title; + char *os; + char *name; +}; + +/* An argument to a macro (multiple values = `It -column'). */ +struct mdoc_argv { + int arg; + int line; + int pos; + size_t sz; + char **value; +}; + +struct mdoc_arg { + size_t argc; + struct mdoc_argv *argv; + unsigned int refcnt; +}; + +/* Node in AST. */ +struct mdoc_node { + struct mdoc_node *parent; + struct mdoc_node *child; + struct mdoc_node *next; + struct mdoc_node *prev; + int nchild; + int line; + int pos; + int tok; + int flags; +#define MDOC_VALID (1 << 0) +#define MDOC_ACTED (1 << 1) + enum mdoc_type type; + enum mdoc_sec sec; + + struct mdoc_arg *args; /* BLOCK/ELEM */ + struct mdoc_node *head; /* BLOCK */ + struct mdoc_node *body; /* BLOCK */ + struct mdoc_node *tail; /* BLOCK */ + char *string; /* TEXT */ +}; + +#define MDOC_IGN_SCOPE (1 << 0) /* Ignore scope violations. */ +#define MDOC_IGN_ESCAPE (1 << 1) /* Ignore bad escape sequences. */ +#define MDOC_IGN_MACRO (1 << 2) /* Ignore unknown macros. */ +#define MDOC_IGN_CHARS (1 << 3) /* Ignore disallowed chars. */ + +/* Call-backs for parse messages. */ + +struct mdoc_cb { + int (*mdoc_err)(void *, int, int, const char *); + int (*mdoc_warn)(void *, int, int, const char *); +}; + +/* See mdoc.3 for documentation. */ + +extern const char *const *mdoc_macronames; +extern const char *const *mdoc_argnames; + +__BEGIN_DECLS + +struct mdoc; + +/* See mdoc.3 for documentation. */ + +void mdoc_free(struct mdoc *); +struct mdoc *mdoc_alloc(void *, int, const struct mdoc_cb *); +int mdoc_reset(struct mdoc *); +int mdoc_parseln(struct mdoc *, int, char *buf); +const struct mdoc_node *mdoc_node(const struct mdoc *); +const struct mdoc_meta *mdoc_meta(const struct mdoc *); +int mdoc_endparse(struct mdoc *); + +__END_DECLS + +#endif /*!MDOC_H*/ diff --git a/usr.bin/mandoc/mdoc_action.c b/usr.bin/mandoc/mdoc_action.c new file mode 100644 index 0000000000..715c2b62dc --- /dev/null +++ b/usr.bin/mandoc/mdoc_action.c @@ -0,0 +1,980 @@ +/* $Id: mdoc_action.c,v 1.23 2009/10/19 16:27:52 schwarze Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include + +#include +#include +#include +#include +#include + +#include "libmdoc.h" + +#define POST_ARGS struct mdoc *m, struct mdoc_node *n +#define PRE_ARGS struct mdoc *m, const struct mdoc_node *n + +struct actions { + int (*pre)(PRE_ARGS); + int (*post)(POST_ARGS); +}; + +static int concat(struct mdoc *, + const struct mdoc_node *, + char *, size_t); +static inline int order_rs(int); + +static int post_ar(POST_ARGS); +static int post_at(POST_ARGS); +static int post_bl(POST_ARGS); +static int post_bl_head(POST_ARGS); +static int post_bl_tagwidth(POST_ARGS); +static int post_bl_width(POST_ARGS); +static int post_dd(POST_ARGS); +static int post_display(POST_ARGS); +static int post_dt(POST_ARGS); +static int post_lb(POST_ARGS); +static int post_nm(POST_ARGS); +static int post_os(POST_ARGS); +static int post_prol(POST_ARGS); +static int post_rs(POST_ARGS); +static int post_sh(POST_ARGS); +static int post_st(POST_ARGS); +static int post_std(POST_ARGS); +static int post_tilde(POST_ARGS); + +static int pre_bd(PRE_ARGS); +static int pre_bl(PRE_ARGS); +static int pre_dl(PRE_ARGS); +static int pre_offset(PRE_ARGS); + +static const struct actions mdoc_actions[MDOC_MAX] = { + { NULL, NULL }, /* Ap */ + { NULL, post_dd }, /* Dd */ + { NULL, post_dt }, /* Dt */ + { NULL, post_os }, /* Os */ + { NULL, post_sh }, /* Sh */ + { NULL, NULL }, /* Ss */ + { NULL, NULL }, /* Pp */ + { NULL, NULL }, /* D1 */ + { pre_dl, post_display }, /* Dl */ + { pre_bd, post_display }, /* Bd */ + { NULL, NULL }, /* Ed */ + { pre_bl, post_bl }, /* Bl */ + { NULL, NULL }, /* El */ + { NULL, NULL }, /* It */ + { NULL, NULL }, /* Ad */ + { NULL, NULL }, /* An */ + { NULL, post_ar }, /* Ar */ + { NULL, NULL }, /* Cd */ + { NULL, NULL }, /* Cm */ + { NULL, NULL }, /* Dv */ + { NULL, NULL }, /* Er */ + { NULL, NULL }, /* Ev */ + { NULL, post_std }, /* Ex */ + { NULL, NULL }, /* Fa */ + { NULL, NULL }, /* Fd */ + { NULL, NULL }, /* Fl */ + { NULL, NULL }, /* Fn */ + { NULL, NULL }, /* Ft */ + { NULL, NULL }, /* Ic */ + { NULL, NULL }, /* In */ + { NULL, NULL }, /* Li */ + { NULL, NULL }, /* Nd */ + { NULL, post_nm }, /* Nm */ + { NULL, NULL }, /* Op */ + { NULL, NULL }, /* Ot */ + { NULL, post_tilde }, /* Pa */ + { NULL, post_std }, /* Rv */ + { NULL, post_st }, /* St */ + { NULL, NULL }, /* Va */ + { NULL, NULL }, /* Vt */ + { NULL, NULL }, /* Xr */ + { NULL, NULL }, /* %A */ + { NULL, NULL }, /* %B */ + { NULL, NULL }, /* %D */ + { NULL, NULL }, /* %I */ + { NULL, NULL }, /* %J */ + { NULL, NULL }, /* %N */ + { NULL, NULL }, /* %O */ + { NULL, NULL }, /* %P */ + { NULL, NULL }, /* %R */ + { NULL, NULL }, /* %T */ + { NULL, NULL }, /* %V */ + { NULL, NULL }, /* Ac */ + { NULL, NULL }, /* Ao */ + { NULL, NULL }, /* Aq */ + { NULL, post_at }, /* At */ + { NULL, NULL }, /* Bc */ + { NULL, NULL }, /* Bf */ + { NULL, NULL }, /* Bo */ + { NULL, NULL }, /* Bq */ + { NULL, NULL }, /* Bsx */ + { NULL, NULL }, /* Bx */ + { NULL, NULL }, /* Db */ + { NULL, NULL }, /* Dc */ + { NULL, NULL }, /* Do */ + { NULL, NULL }, /* Dq */ + { NULL, NULL }, /* Ec */ + { NULL, NULL }, /* Ef */ + { NULL, NULL }, /* Em */ + { NULL, NULL }, /* Eo */ + { NULL, NULL }, /* Fx */ + { NULL, NULL }, /* Ms */ + { NULL, NULL }, /* No */ + { NULL, NULL }, /* Ns */ + { NULL, NULL }, /* Nx */ + { NULL, NULL }, /* Ox */ + { NULL, NULL }, /* Pc */ + { NULL, NULL }, /* Pf */ + { NULL, NULL }, /* Po */ + { NULL, NULL }, /* Pq */ + { NULL, NULL }, /* Qc */ + { NULL, NULL }, /* Ql */ + { NULL, NULL }, /* Qo */ + { NULL, NULL }, /* Qq */ + { NULL, NULL }, /* Re */ + { NULL, post_rs }, /* Rs */ + { NULL, NULL }, /* Sc */ + { NULL, NULL }, /* So */ + { NULL, NULL }, /* Sq */ + { NULL, NULL }, /* Sm */ + { NULL, NULL }, /* Sx */ + { NULL, NULL }, /* Sy */ + { NULL, NULL }, /* Tn */ + { NULL, NULL }, /* Ux */ + { NULL, NULL }, /* Xc */ + { NULL, NULL }, /* Xo */ + { NULL, NULL }, /* Fo */ + { NULL, NULL }, /* Fc */ + { NULL, NULL }, /* Oo */ + { NULL, NULL }, /* Oc */ + { NULL, NULL }, /* Bk */ + { NULL, NULL }, /* Ek */ + { NULL, NULL }, /* Bt */ + { NULL, NULL }, /* Hf */ + { NULL, NULL }, /* Fr */ + { NULL, NULL }, /* Ud */ + { NULL, post_lb }, /* Lb */ + { NULL, NULL }, /* Lp */ + { NULL, post_tilde }, /* Lk */ + { NULL, NULL }, /* Mt */ + { NULL, NULL }, /* Brq */ + { NULL, NULL }, /* Bro */ + { NULL, NULL }, /* Brc */ + { NULL, NULL }, /* %C */ + { NULL, NULL }, /* Es */ + { NULL, NULL }, /* En */ + { NULL, NULL }, /* Dx */ + { NULL, NULL }, /* %Q */ + { NULL, NULL }, /* br */ + { NULL, NULL }, /* sp */ +}; + +#define RSORD_MAX 13 + +static const int rsord[RSORD_MAX] = { + MDOC__A, + MDOC__T, + MDOC__B, + MDOC__I, + MDOC__J, + MDOC__R, + MDOC__N, + MDOC__V, + MDOC__P, + MDOC__Q, + MDOC__D, + MDOC__O, + MDOC__C +}; + + +int +mdoc_action_pre(struct mdoc *m, const struct mdoc_node *n) +{ + + switch (n->type) { + case (MDOC_ROOT): + /* FALLTHROUGH */ + case (MDOC_TEXT): + return(1); + default: + break; + } + + if (NULL == mdoc_actions[n->tok].pre) + return(1); + return((*mdoc_actions[n->tok].pre)(m, n)); +} + + +int +mdoc_action_post(struct mdoc *m) +{ + + if (MDOC_ACTED & m->last->flags) + return(1); + m->last->flags |= MDOC_ACTED; + + switch (m->last->type) { + case (MDOC_TEXT): + /* FALLTHROUGH */ + case (MDOC_ROOT): + return(1); + default: + break; + } + + if (NULL == mdoc_actions[m->last->tok].post) + return(1); + return((*mdoc_actions[m->last->tok].post)(m, m->last)); +} + + +static int +concat(struct mdoc *m, const struct mdoc_node *n, + char *buf, size_t sz) +{ + + for ( ; n; n = n->next) { + assert(MDOC_TEXT == n->type); + if (strlcat(buf, n->string, sz) >= sz) + return(mdoc_nerr(m, n, ETOOLONG)); + if (NULL == n->next) + continue; + if (strlcat(buf, " ", sz) >= sz) + return(mdoc_nerr(m, n, ETOOLONG)); + } + + return(1); +} + + +static int +post_std(POST_ARGS) +{ + struct mdoc_node *nn; + + if (n->child) + return(1); + + nn = n; + m->next = MDOC_NEXT_CHILD; + assert(m->meta.name); + if ( ! mdoc_word_alloc(m, n->line, n->pos, m->meta.name)) + return(0); + m->last = nn; + + return(1); +} + + +static int +post_nm(POST_ARGS) +{ + char buf[64]; + + if (m->meta.name) + return(1); + + buf[0] = 0; + if ( ! concat(m, n->child, buf, sizeof(buf))) + return(0); + if (NULL == (m->meta.name = strdup(buf))) + return(mdoc_nerr(m, n, EMALLOC)); + + return(1); +} + + +static int +post_lb(POST_ARGS) +{ + const char *p; + char *buf; + size_t sz; + + assert(MDOC_TEXT == n->child->type); + p = mdoc_a2lib(n->child->string); + if (NULL == p) { + sz = strlen(n->child->string) + + 2 + strlen("\\(lqlibrary\\(rq"); + buf = malloc(sz); + if (NULL == buf) + return(mdoc_nerr(m, n, EMALLOC)); + (void)snprintf(buf, sz, "library \\(lq%s\\(rq", + n->child->string); + free(n->child->string); + n->child->string = buf; + return(1); + } + + free(n->child->string); + n->child->string = strdup(p); + if (NULL == n->child->string) + return(mdoc_nerr(m, n, EMALLOC)); + + return(1); +} + + +static int +post_st(POST_ARGS) +{ + const char *p; + + assert(MDOC_TEXT == n->child->type); + p = mdoc_a2st(n->child->string); + assert(p); + free(n->child->string); + n->child->string = strdup(p); + if (NULL == n->child->string) + return(mdoc_nerr(m, n, EMALLOC)); + + return(1); +} + + +static int +post_at(POST_ARGS) +{ + struct mdoc_node *nn; + const char *p; + + if (n->child) { + assert(MDOC_TEXT == n->child->type); + p = mdoc_a2att(n->child->string); + assert(p); + free(n->child->string); + n->child->string = strdup(p); + if (NULL == n->child->string) + return(mdoc_nerr(m, n, EMALLOC)); + return(1); + } + + nn = n; + m->next = MDOC_NEXT_CHILD; + + if ( ! mdoc_word_alloc(m, nn->line, nn->pos, "AT&T UNIX")) + return(0); + m->last = nn; + + return(1); +} + + +static int +post_sh(POST_ARGS) +{ + enum mdoc_sec sec; + char buf[64]; + + /* + * We keep track of the current section /and/ the "named" + * section, which is one of the conventional ones, in order to + * check ordering. + */ + + if (MDOC_HEAD != n->type) + return(1); + + buf[0] = 0; + if ( ! concat(m, n->child, buf, sizeof(buf))) + return(0); + if (SEC_CUSTOM != (sec = mdoc_atosec(buf))) + m->lastnamed = sec; + + switch ((m->lastsec = sec)) { + case (SEC_RETURN_VALUES): + /* FALLTHROUGH */ + case (SEC_ERRORS): + switch (m->meta.msec) { + case (2): + /* FALLTHROUGH */ + case (3): + /* FALLTHROUGH */ + case (9): + break; + default: + return(mdoc_nwarn(m, n, EBADSEC)); + } + break; + default: + break; + } + return(1); +} + + +static int +post_dt(POST_ARGS) +{ + struct mdoc_node *nn; + const char *cp; + char *ep; + long lval; + + if (m->meta.title) + free(m->meta.title); + if (m->meta.vol) + free(m->meta.vol); + if (m->meta.arch) + free(m->meta.arch); + + m->meta.title = m->meta.vol = m->meta.arch = NULL; + m->meta.msec = 0; + + /* Handles: `.Dt' + * --> title = unknown, volume = local, msec = 0, arch = NULL + */ + + if (NULL == (nn = n->child)) { + if (NULL == (m->meta.title = strdup("unknown"))) + return(mdoc_nerr(m, n, EMALLOC)); + if (NULL == (m->meta.vol = strdup("local"))) + return(mdoc_nerr(m, n, EMALLOC)); + return(post_prol(m, n)); + } + + /* Handles: `.Dt TITLE' + * --> title = TITLE, volume = local, msec = 0, arch = NULL + */ + + if (NULL == (m->meta.title = strdup(nn->string))) + return(mdoc_nerr(m, n, EMALLOC)); + + if (NULL == (nn = nn->next)) { + if (NULL == (m->meta.vol = strdup("local"))) + return(mdoc_nerr(m, n, EMALLOC)); + return(post_prol(m, n)); + } + + /* Handles: `.Dt TITLE SEC' + * --> title = TITLE, volume = SEC is msec ? + * format(msec) : SEC, + * msec = SEC is msec ? atoi(msec) : 0, + * arch = NULL + */ + + cp = mdoc_a2msec(nn->string); + if (cp) { + if (NULL == (m->meta.vol = strdup(cp))) + return(mdoc_nerr(m, n, EMALLOC)); + errno = 0; + lval = strtol(nn->string, &ep, 10); + if (nn->string[0] != '\0' && *ep == '\0') + m->meta.msec = (int)lval; + } else if (NULL == (m->meta.vol = strdup(nn->string))) + return(mdoc_nerr(m, n, EMALLOC)); + + if (NULL == (nn = nn->next)) + return(post_prol(m, n)); + + /* Handles: `.Dt TITLE SEC VOL' + * --> title = TITLE, volume = VOL is vol ? + * format(VOL) : + * VOL is arch ? format(arch) : + * VOL + */ + + cp = mdoc_a2vol(nn->string); + if (cp) { + free(m->meta.vol); + if (NULL == (m->meta.vol = strdup(cp))) + return(mdoc_nerr(m, n, EMALLOC)); + } else { + cp = mdoc_a2arch(nn->string); + if (NULL == cp) { + free(m->meta.vol); + if (NULL == (m->meta.vol = strdup(nn->string))) + return(mdoc_nerr(m, n, EMALLOC)); + } else if (NULL == (m->meta.arch = strdup(cp))) + return(mdoc_nerr(m, n, EMALLOC)); + } + + /* Ignore any subsequent parameters... */ + + return(post_prol(m, n)); +} + + +static int +post_os(POST_ARGS) +{ + char buf[64]; + struct utsname utsname; + + if (m->meta.os) + free(m->meta.os); + + buf[0] = 0; + if ( ! concat(m, n->child, buf, sizeof(buf))) + return(0); + + if (0 == buf[0]) { + if (-1 == uname(&utsname)) + return(mdoc_nerr(m, n, EUTSNAME)); + if (strlcat(buf, utsname.sysname, 64) >= 64) + return(mdoc_nerr(m, n, ETOOLONG)); + if (strlcat(buf, " ", 64) >= 64) + return(mdoc_nerr(m, n, ETOOLONG)); + if (strlcat(buf, utsname.release, 64) >= 64) + return(mdoc_nerr(m, n, ETOOLONG)); + } + + if (NULL == (m->meta.os = strdup(buf))) + return(mdoc_nerr(m, n, EMALLOC)); + + return(post_prol(m, n)); +} + + +/* + * Calculate the -width for a `Bl -tag' list if it hasn't been provided. + * Uses the first head macro. + */ +static int +post_bl_tagwidth(POST_ARGS) +{ + struct mdoc_node *nn; + int sz; + char buf[32]; + + /* + * Use the text width, if a text node, or the default macro + * width if a macro. + */ + + nn = n->body->child; + if (nn) { + assert(MDOC_BLOCK == nn->type); + assert(MDOC_It == nn->tok); + nn = nn->head->child; + } + + sz = 10; /* Default size. */ + + if (nn) { + if (MDOC_TEXT != nn->type) { + if (0 == (sz = (int)mdoc_macro2len(nn->tok))) + if ( ! mdoc_nwarn(m, n, ENOWIDTH)) + return(0); + } else + sz = (int)strlen(nn->string) + 1; + } + + if (-1 == snprintf(buf, sizeof(buf), "%dn", sz)) + return(mdoc_nerr(m, n, ENUMFMT)); + + /* + * We have to dynamically add this to the macro's argument list. + * We're guaranteed that a MDOC_Width doesn't already exist. + */ + + nn = n; + assert(nn->args); + sz = (int)(nn->args->argc)++; + + nn->args->argv = realloc(nn->args->argv, + nn->args->argc * sizeof(struct mdoc_argv)); + + if (NULL == nn->args->argv) + return(mdoc_nerr(m, n, EMALLOC)); + + nn->args->argv[sz].arg = MDOC_Width; + nn->args->argv[sz].line = n->line; + nn->args->argv[sz].pos = n->pos; + nn->args->argv[sz].sz = 1; + nn->args->argv[sz].value = calloc(1, sizeof(char *)); + + if (NULL == nn->args->argv[sz].value) + return(mdoc_nerr(m, n, EMALLOC)); + if (NULL == (nn->args->argv[sz].value[0] = strdup(buf))) + return(mdoc_nerr(m, n, EMALLOC)); + + return(1); +} + + +static int +post_bl_width(POST_ARGS) +{ + size_t width; + int i, tok; + char buf[32]; + char *p; + + if (NULL == n->args) + return(1); + + for (i = 0; i < (int)n->args->argc; i++) + if (MDOC_Width == n->args->argv[i].arg) + break; + + if (i == (int)n->args->argc) + return(1); + p = n->args->argv[i].value[0]; + + /* + * If the value to -width is a macro, then we re-write it to be + * the macro's width as set in share/tmac/mdoc/doc-common. + */ + + if (0 == strcmp(p, "Ds")) + width = 6; + else if (MDOC_MAX == (tok = mdoc_hash_find(p))) + return(1); + else if (0 == (width = mdoc_macro2len(tok))) + return(mdoc_nwarn(m, n, ENOWIDTH)); + + /* The value already exists: free and reallocate it. */ + + if (-1 == snprintf(buf, sizeof(buf), "%zun", width)) + return(mdoc_nerr(m, n, ENUMFMT)); + + free(n->args->argv[i].value[0]); + n->args->argv[i].value[0] = strdup(buf); + if (NULL == n->args->argv[i].value[0]) + return(mdoc_nerr(m, n, EMALLOC)); + + return(1); +} + + +/* ARGSUSED */ +static int +post_bl_head(POST_ARGS) +{ + int i, c; + struct mdoc_node *np, *nn, *nnp; + + if (NULL == n->child) + return(1); + + np = n->parent; + assert(np->args); + + for (c = 0; c < (int)np->args->argc; c++) + if (MDOC_Column == np->args->argv[c].arg) + break; + + /* Only process -column. */ + + if (c == (int)np->args->argc) + return(1); + + assert(0 == np->args->argv[c].sz); + + /* + * Accomodate for new-style groff column syntax. Shuffle the + * child nodes, all of which must be TEXT, as arguments for the + * column field. Then, delete the head children. + */ + + np->args->argv[c].sz = (size_t)n->nchild; + np->args->argv[c].value = malloc + ((size_t)n->nchild * sizeof(char *)); + + for (i = 0, nn = n->child; nn; i++) { + np->args->argv[c].value[i] = nn->string; + nn->string = NULL; + nnp = nn; + nn = nn->next; + mdoc_node_free(nnp); + } + + n->nchild = 0; + n->child = NULL; + + return(1); +} + + +static int +post_bl(POST_ARGS) +{ + int i, r, len; + + if (MDOC_HEAD == n->type) + return(post_bl_head(m, n)); + if (MDOC_BLOCK != n->type) + return(1); + + /* + * These are fairly complicated, so we've broken them into two + * functions. post_bl_tagwidth() is called when a -tag is + * specified, but no -width (it must be guessed). The second + * when a -width is specified (macro indicators must be + * rewritten into real lengths). + */ + + len = (int)(n->args ? n->args->argc : 0); + + for (r = i = 0; i < len; i++) { + if (MDOC_Tag == n->args->argv[i].arg) + r |= 1 << 0; + if (MDOC_Width == n->args->argv[i].arg) + r |= 1 << 1; + } + + if (r & (1 << 0) && ! (r & (1 << 1))) { + if ( ! post_bl_tagwidth(m, n)) + return(0); + } else if (r & (1 << 1)) + if ( ! post_bl_width(m, n)) + return(0); + + return(1); +} + + +static int +post_tilde(POST_ARGS) +{ + struct mdoc_node *np; + + if (n->child) + return(1); + + np = n; + m->next = MDOC_NEXT_CHILD; + + /* XXX: not documented for `Lk'. */ + if ( ! mdoc_word_alloc(m, n->line, n->pos, "~")) + return(0); + m->last = np; + + return(1); +} + + +static int +post_ar(POST_ARGS) +{ + struct mdoc_node *np; + + if (n->child) + return(1); + + np = n; + m->next = MDOC_NEXT_CHILD; + if ( ! mdoc_word_alloc(m, n->line, n->pos, "file")) + return(0); + if ( ! mdoc_word_alloc(m, n->line, n->pos, "...")) + return(0); + m->last = np; + + return(1); +} + + +static int +post_dd(POST_ARGS) +{ + char buf[64]; + + buf[0] = 0; + if ( ! concat(m, n->child, buf, sizeof(buf))) + return(0); + + if (0 == (m->meta.date = mdoc_atotime(buf))) { + if ( ! mdoc_nwarn(m, n, EBADDATE)) + return(0); + m->meta.date = time(NULL); + } + + return(post_prol(m, n)); +} + + +static int +post_prol(POST_ARGS) +{ + struct mdoc_node *np; + + /* Remove prologue macros from AST. */ + + if (n->parent->child == n) + n->parent->child = n->prev; + if (n->prev) + n->prev->next = NULL; + + np = n; + assert(NULL == n->next); + + if (n->prev) { + m->last = n->prev; + m->next = MDOC_NEXT_SIBLING; + } else { + m->last = n->parent; + m->next = MDOC_NEXT_CHILD; + } + + mdoc_node_freelist(np); + + if (m->meta.title && m->meta.date && m->meta.os) + m->flags |= MDOC_PBODY; + + return(1); +} + + +static int +pre_dl(PRE_ARGS) +{ + + if (MDOC_BODY == n->type) + m->flags |= MDOC_LITERAL; + return(1); +} + + +static int +pre_offset(PRE_ARGS) +{ + int i; + + /* + * Make sure that an empty offset produces an 8n length space as + * stipulated by mdoc.samples. + */ + + assert(n->args); + for (i = 0; i < (int)n->args->argc; i++) { + if (MDOC_Offset != n->args->argv[i].arg) + continue; + if (n->args->argv[i].sz) + break; + assert(1 == n->args->refcnt); + /* If no value set, length of . */ + n->args->argv[i].value = + calloc(1, sizeof(char *)); + if (NULL == n->args->argv[i].value) + return(mdoc_nerr(m, n, EMALLOC)); + n->args->argv[i].sz++; + n->args->argv[i].value[0] = strdup("8n"); + if (NULL == n->args->argv[i].value[0]) + return(mdoc_nerr(m, n, EMALLOC)); + break; + } + + return(1); +} + + +static int +pre_bl(PRE_ARGS) +{ + + return(MDOC_BLOCK == n->type ? pre_offset(m, n) : 1); +} + + +static int +pre_bd(PRE_ARGS) +{ + int i; + + if (MDOC_BLOCK == n->type) + return(pre_offset(m, n)); + if (MDOC_BODY != n->type) + return(1); + + /* Enter literal context if `Bd -literal' or `-unfilled'. */ + + for (n = n->parent, i = 0; i < (int)n->args->argc; i++) + if (MDOC_Literal == n->args->argv[i].arg) + m->flags |= MDOC_LITERAL; + else if (MDOC_Unfilled == n->args->argv[i].arg) + m->flags |= MDOC_LITERAL; + + return(1); +} + + +static int +post_display(POST_ARGS) +{ + + if (MDOC_BODY == n->type) + m->flags &= ~MDOC_LITERAL; + return(1); +} + + +static inline int +order_rs(int t) +{ + int i; + + for (i = 0; i < RSORD_MAX; i++) + if (rsord[i] == t) + return(i); + + abort(); + /* NOTREACHED */ +} + + +/* ARGSUSED */ +static int +post_rs(POST_ARGS) +{ + struct mdoc_node *nn, *next, *prev; + int o; + + if (MDOC_BLOCK != n->type) + return(1); + + assert(n->body->child); + for (next = NULL, nn = n->body->child->next; nn; nn = next) { + o = order_rs(nn->tok); + + /* Remove `nn' from the chain. */ + next = nn->next; + if (next) + next->prev = nn->prev; + + prev = nn->prev; + if (prev) + prev->next = nn->next; + + nn->prev = nn->next = NULL; + + /* + * Scan back until we reach a node that's ordered before + * us, then set ourselves as being the next. + */ + for ( ; prev; prev = prev->prev) + if (order_rs(prev->tok) <= o) + break; + + nn->prev = prev; + if (prev) { + if (prev->next) + prev->next->prev = nn; + nn->next = prev->next; + prev->next = nn; + continue; + } + + n->body->child->prev = nn; + nn->next = n->body->child; + n->body->child = nn; + } + return(1); +} diff --git a/usr.bin/mandoc/mdoc_argv.c b/usr.bin/mandoc/mdoc_argv.c new file mode 100644 index 0000000000..18085a8759 --- /dev/null +++ b/usr.bin/mandoc/mdoc_argv.c @@ -0,0 +1,771 @@ +/* $Id: mdoc_argv.c,v 1.17 2009/10/21 19:13:50 schwarze Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include + +#include +#include +#include +#include +#include + +#include "libmdoc.h" + +/* + * Routines to parse arguments of macros. Arguments follow the syntax + * of `-arg [val [valN...]]'. Arguments come in all types: quoted + * arguments, multiple arguments per value, no-value arguments, etc. + * + * There's no limit to the number or arguments that may be allocated. + */ + +#define ARGV_NONE (1 << 0) +#define ARGV_SINGLE (1 << 1) +#define ARGV_MULTI (1 << 2) +#define ARGV_OPT_SINGLE (1 << 3) + +#define MULTI_STEP 5 + +static int argv_a2arg(int, const char *); +static int args(struct mdoc *, int, int *, + char *, int, char **); +static int argv(struct mdoc *, int, + struct mdoc_argv *, int *, char *); +static int argv_single(struct mdoc *, int, + struct mdoc_argv *, int *, char *); +static int argv_opt_single(struct mdoc *, int, + struct mdoc_argv *, int *, char *); +static int argv_multi(struct mdoc *, int, + struct mdoc_argv *, int *, char *); + +/* Per-argument flags. */ + +static int mdoc_argvflags[MDOC_ARG_MAX] = { + ARGV_NONE, /* MDOC_Split */ + ARGV_NONE, /* MDOC_Nosplit */ + ARGV_NONE, /* MDOC_Ragged */ + ARGV_NONE, /* MDOC_Unfilled */ + ARGV_NONE, /* MDOC_Literal */ + ARGV_SINGLE, /* MDOC_File */ + ARGV_OPT_SINGLE, /* MDOC_Offset */ + ARGV_NONE, /* MDOC_Bullet */ + ARGV_NONE, /* MDOC_Dash */ + ARGV_NONE, /* MDOC_Hyphen */ + ARGV_NONE, /* MDOC_Item */ + ARGV_NONE, /* MDOC_Enum */ + ARGV_NONE, /* MDOC_Tag */ + ARGV_NONE, /* MDOC_Diag */ + ARGV_NONE, /* MDOC_Hang */ + ARGV_NONE, /* MDOC_Ohang */ + ARGV_NONE, /* MDOC_Inset */ + ARGV_MULTI, /* MDOC_Column */ + ARGV_SINGLE, /* MDOC_Width */ + ARGV_NONE, /* MDOC_Compact */ + ARGV_NONE, /* MDOC_Std */ + ARGV_NONE, /* MDOC_Filled */ + ARGV_NONE, /* MDOC_Words */ + ARGV_NONE, /* MDOC_Emphasis */ + ARGV_NONE, /* MDOC_Symbolic */ + ARGV_NONE /* MDOC_Symbolic */ +}; + +static int mdoc_argflags[MDOC_MAX] = { + 0, /* Ap */ + 0, /* Dd */ + 0, /* Dt */ + 0, /* Os */ + 0, /* Sh */ + 0, /* Ss */ + ARGS_DELIM, /* Pp */ + ARGS_DELIM, /* D1 */ + ARGS_DELIM, /* Dl */ + 0, /* Bd */ + 0, /* Ed */ + 0, /* Bl */ + 0, /* El */ + 0, /* It */ + ARGS_DELIM, /* Ad */ + ARGS_DELIM, /* An */ + ARGS_DELIM, /* Ar */ + 0, /* Cd */ + ARGS_DELIM, /* Cm */ + ARGS_DELIM, /* Dv */ + ARGS_DELIM, /* Er */ + ARGS_DELIM, /* Ev */ + 0, /* Ex */ + ARGS_DELIM, /* Fa */ + 0, /* Fd */ + ARGS_DELIM, /* Fl */ + ARGS_DELIM, /* Fn */ + ARGS_DELIM, /* Ft */ + ARGS_DELIM, /* Ic */ + 0, /* In */ + ARGS_DELIM, /* Li */ + 0, /* Nd */ + ARGS_DELIM, /* Nm */ + ARGS_DELIM, /* Op */ + 0, /* Ot */ + ARGS_DELIM, /* Pa */ + 0, /* Rv */ + ARGS_DELIM, /* St */ + ARGS_DELIM, /* Va */ + ARGS_DELIM, /* Vt */ + ARGS_DELIM, /* Xr */ + 0, /* %A */ + 0, /* %B */ + 0, /* %D */ + 0, /* %I */ + 0, /* %J */ + 0, /* %N */ + 0, /* %O */ + 0, /* %P */ + 0, /* %R */ + 0, /* %T */ + 0, /* %V */ + ARGS_DELIM, /* Ac */ + 0, /* Ao */ + ARGS_DELIM, /* Aq */ + ARGS_DELIM, /* At */ + ARGS_DELIM, /* Bc */ + 0, /* Bf */ + 0, /* Bo */ + ARGS_DELIM, /* Bq */ + ARGS_DELIM, /* Bsx */ + ARGS_DELIM, /* Bx */ + 0, /* Db */ + ARGS_DELIM, /* Dc */ + 0, /* Do */ + ARGS_DELIM, /* Dq */ + ARGS_DELIM, /* Ec */ + 0, /* Ef */ + ARGS_DELIM, /* Em */ + 0, /* Eo */ + ARGS_DELIM, /* Fx */ + ARGS_DELIM, /* Ms */ + ARGS_DELIM, /* No */ + ARGS_DELIM, /* Ns */ + ARGS_DELIM, /* Nx */ + ARGS_DELIM, /* Ox */ + ARGS_DELIM, /* Pc */ + ARGS_DELIM, /* Pf */ + 0, /* Po */ + ARGS_DELIM, /* Pq */ + ARGS_DELIM, /* Qc */ + ARGS_DELIM, /* Ql */ + 0, /* Qo */ + ARGS_DELIM, /* Qq */ + 0, /* Re */ + 0, /* Rs */ + ARGS_DELIM, /* Sc */ + 0, /* So */ + ARGS_DELIM, /* Sq */ + 0, /* Sm */ + ARGS_DELIM, /* Sx */ + ARGS_DELIM, /* Sy */ + ARGS_DELIM, /* Tn */ + ARGS_DELIM, /* Ux */ + ARGS_DELIM, /* Xc */ + 0, /* Xo */ + 0, /* Fo */ + 0, /* Fc */ + 0, /* Oo */ + ARGS_DELIM, /* Oc */ + 0, /* Bk */ + 0, /* Ek */ + 0, /* Bt */ + 0, /* Hf */ + 0, /* Fr */ + 0, /* Ud */ + 0, /* Lb */ + ARGS_DELIM, /* Lp */ + ARGS_DELIM, /* Lk */ + ARGS_DELIM, /* Mt */ + ARGS_DELIM, /* Brq */ + 0, /* Bro */ + ARGS_DELIM, /* Brc */ + 0, /* %C */ + 0, /* Es */ + 0, /* En */ + 0, /* Dx */ + 0, /* %Q */ + 0, /* br */ + 0, /* sp */ +}; + + +/* + * Parse an argument from line text. This comes in the form of -key + * [value0...], which may either have a single mandatory value, at least + * one mandatory value, an optional single value, or no value. + */ +int +mdoc_argv(struct mdoc *m, int line, int tok, + struct mdoc_arg **v, int *pos, char *buf) +{ + char *p, sv; + struct mdoc_argv tmp; + struct mdoc_arg *arg; + + if (0 == buf[*pos]) + return(ARGV_EOLN); + + assert(' ' != buf[*pos]); + + /* Parse through to the first unescaped space. */ + + p = &buf[++(*pos)]; + + assert(*pos > 0); + + /* LINTED */ + while (buf[*pos]) { + if (' ' == buf[*pos]) + if ('\\' != buf[*pos - 1]) + break; + (*pos)++; + } + + /* XXX - save zeroed byte, if not an argument. */ + + sv = 0; + if (buf[*pos]) { + sv = buf[*pos]; + buf[(*pos)++] = 0; + } + + (void)memset(&tmp, 0, sizeof(struct mdoc_argv)); + tmp.line = line; + tmp.pos = *pos; + + /* See if our token accepts the argument. */ + + if (MDOC_ARG_MAX == (tmp.arg = argv_a2arg(tok, p))) { + /* XXX - restore saved zeroed byte. */ + if (sv) + buf[*pos - 1] = sv; + return(ARGV_WORD); + } + + while (buf[*pos] && ' ' == buf[*pos]) + (*pos)++; + + if ( ! argv(m, line, &tmp, pos, buf)) + return(ARGV_ERROR); + + if (NULL == (arg = *v)) { + *v = calloc(1, sizeof(struct mdoc_arg)); + if (NULL == *v) { + (void)mdoc_nerr(m, m->last, EMALLOC); + return(ARGV_ERROR); + } + arg = *v; + } + + arg->argc++; + arg->argv = realloc(arg->argv, arg->argc * + sizeof(struct mdoc_argv)); + + if (NULL == arg->argv) { + (void)mdoc_nerr(m, m->last, EMALLOC); + return(ARGV_ERROR); + } + + (void)memcpy(&arg->argv[(int)arg->argc - 1], + &tmp, sizeof(struct mdoc_argv)); + + return(ARGV_ARG); +} + + +void +mdoc_argv_free(struct mdoc_arg *p) +{ + int i, j; + + if (NULL == p) + return; + + if (p->refcnt) { + --(p->refcnt); + if (p->refcnt) + return; + } + assert(p->argc); + + /* LINTED */ + for (i = 0; i < (int)p->argc; i++) { + if (0 == p->argv[i].sz) + continue; + if (NULL == p->argv[i].value) + continue; + + /* LINTED */ + for (j = 0; j < (int)p->argv[i].sz; j++) + if (p->argv[i].value[j]) + free(p->argv[i].value[j]); + + free(p->argv[i].value); + } + + free(p->argv); + free(p); +} + + +int +mdoc_zargs(struct mdoc *m, int line, int *pos, + char *buf, int flags, char **v) +{ + + return(args(m, line, pos, buf, flags, v)); +} + + +int +mdoc_args(struct mdoc *m, int line, + int *pos, char *buf, int tok, char **v) +{ + int fl, c, i; + struct mdoc_node *n; + + fl = (0 == tok) ? 0 : mdoc_argflags[tok]; + + if (MDOC_It != tok) + return(args(m, line, pos, buf, fl, v)); + + /* + * The `It' macro is a special case, as it acquires parameters from its + * parent `Bl' context, specifically, we're concerned with -column. + */ + + for (n = m->last; n; n = n->parent) + if (MDOC_BLOCK == n->type && MDOC_Bl == n->tok) + break; + + assert(n); + c = (int)(n->args ? n->args->argc : 0); + assert(c > 0); + + /* LINTED */ + for (i = 0; i < c; i++) { + if (MDOC_Column != n->args->argv[i].arg) + continue; + fl |= ARGS_TABSEP; + fl &= ~ARGS_DELIM; + break; + } + + return(args(m, line, pos, buf, fl, v)); +} + + +static int +args(struct mdoc *m, int line, int *pos, + char *buf, int fl, char **v) +{ + int i; + char *p, *pp; + + /* + * Parse out the terms (like `val' in `.Xx -arg val' or simply + * `.Xx val'), which can have all sorts of properties: + * + * ARGS_DELIM: use special handling if encountering trailing + * delimiters in the form of [[::delim::][ ]+]+. + * + * ARGS_NOWARN: don't post warnings. This is only used when + * re-parsing delimiters, as the warnings have already been + * posted. + * + * ARGS_TABSEP: use special handling for tab/`Ta' separated + * phrases like in `Bl -column'. + */ + + assert(*pos); + assert(' ' != buf[*pos]); + + if (0 == buf[*pos]) + return(ARGS_EOLN); + + /* + * If the first character is a delimiter and we're to look for + * delimited strings, then pass down the buffer seeing if it + * follows the pattern of [[::delim::][ ]+]+. + */ + + if ((fl & ARGS_DELIM) && mdoc_iscdelim(buf[*pos])) { + for (i = *pos; buf[i]; ) { + if ( ! mdoc_iscdelim(buf[i])) + break; + i++; + if (0 == buf[i] || ' ' != buf[i]) + break; + i++; + while (buf[i] && ' ' == buf[i]) + i++; + } + + if (0 == buf[i]) { + *v = &buf[*pos]; + if (' ' != buf[i - 1]) + return(ARGS_PUNCT); + if (ARGS_NOWARN & fl) + return(ARGS_PUNCT); + if ( ! mdoc_pwarn(m, line, *pos, ETAILWS)) + return(ARGS_ERROR); + return(ARGS_PUNCT); + } + } + + *v = &buf[*pos]; + + /* + * First handle TABSEP items, restricted to `Bl -column'. This + * ignores conventional token parsing and instead uses tabs or + * `Ta' macros to separate phrases. Phrases are parsed again + * for arguments at a later phase. + */ + + if (ARGS_TABSEP & fl) { + /* Scan ahead to tab (can't be escaped). */ + p = strchr(*v, '\t'); + + /* Scan ahead to unescaped `Ta'. */ + for (pp = *v; ; pp++) { + if (NULL == (pp = strstr(pp, "Ta"))) + break; + if (pp > *v && ' ' != *(pp - 1)) + continue; + if (' ' == *(pp + 2) || 0 == *(pp + 2)) + break; + } + + /* + * Adjust new-buffer position to be beyond delimiter + * mark (e.g., Ta -> end + 2). + */ + if (p && pp) { + *pos += pp < p ? 2 : 1; + p = pp < p ? pp : p; + } else if (p && ! pp) { + *pos += 1; + } else if (pp && ! p) { + p = pp; + *pos += 2; + } else + p = strchr(*v, 0); + + /* Whitespace check for eoln case... */ + if (0 == *p && ' ' == *(p - 1) && ! (ARGS_NOWARN & fl)) + if ( ! mdoc_pwarn(m, line, *pos, ETAILWS)) + return(ARGS_ERROR); + + *pos += (int)(p - *v); + + /* Strip delimiter's preceding whitespace. */ + pp = p - 1; + while (pp > *v && ' ' == *pp) { + if (pp > *v && '\\' == *(pp - 1)) + break; + pp--; + } + *(pp + 1) = 0; + + /* Strip delimiter's proceeding whitespace. */ + for (pp = &buf[*pos]; ' ' == *pp; pp++, (*pos)++) + /* Skip ahead. */ ; + + return(ARGS_PHRASE); + } + + /* + * Process a quoted literal. A quote begins with a double-quote + * and ends with a double-quote NOT preceded by a double-quote. + * Whitespace is NOT involved in literal termination. + */ + + if ('\"' == buf[*pos]) { + *v = &buf[++(*pos)]; + + for ( ; buf[*pos]; (*pos)++) { + if ('\"' != buf[*pos]) + continue; + if ('\"' != buf[*pos + 1]) + break; + (*pos)++; + } + + if (0 == buf[*pos]) { + if (ARGS_NOWARN & fl) + return(ARGS_QWORD); + if ( ! mdoc_pwarn(m, line, *pos, EQUOTTERM)) + return(ARGS_ERROR); + return(ARGS_QWORD); + } + + buf[(*pos)++] = 0; + + if (0 == buf[*pos]) + return(ARGS_QWORD); + + while (' ' == buf[*pos]) + (*pos)++; + + if (0 == buf[*pos] && ! (ARGS_NOWARN & fl)) + if ( ! mdoc_pwarn(m, line, *pos, ETAILWS)) + return(ARGS_ERROR); + + return(ARGS_QWORD); + } + + /* + * A non-quoted term progresses until either the end of line or + * a non-escaped whitespace. + */ + + for ( ; buf[*pos]; (*pos)++) + if (' ' == buf[*pos] && '\\' != buf[*pos - 1]) + break; + + if (0 == buf[*pos]) + return(ARGS_WORD); + + buf[(*pos)++] = 0; + + while (' ' == buf[*pos]) + (*pos)++; + + if (0 == buf[*pos] && ! (ARGS_NOWARN & fl)) + if ( ! mdoc_pwarn(m, line, *pos, ETAILWS)) + return(ARGS_ERROR); + + return(ARGS_WORD); +} + + +static int +argv_a2arg(int tok, const char *p) +{ + + /* + * Parse an argument identifier from its text. XXX - this + * should really be table-driven to clarify the code. + * + * If you add an argument to the list, make sure that you + * register it here with its one or more macros! + */ + + switch (tok) { + case (MDOC_An): + if (0 == strcmp(p, "split")) + return(MDOC_Split); + else if (0 == strcmp(p, "nosplit")) + return(MDOC_Nosplit); + break; + + case (MDOC_Bd): + if (0 == strcmp(p, "ragged")) + return(MDOC_Ragged); + else if (0 == strcmp(p, "unfilled")) + return(MDOC_Unfilled); + else if (0 == strcmp(p, "filled")) + return(MDOC_Filled); + else if (0 == strcmp(p, "literal")) + return(MDOC_Literal); + else if (0 == strcmp(p, "file")) + return(MDOC_File); + else if (0 == strcmp(p, "offset")) + return(MDOC_Offset); + else if (0 == strcmp(p, "compact")) + return(MDOC_Compact); + else if (0 == strcmp(p, "centered")) + return(MDOC_Centred); + break; + + case (MDOC_Bf): + if (0 == strcmp(p, "emphasis")) + return(MDOC_Emphasis); + else if (0 == strcmp(p, "literal")) + return(MDOC_Literal); + else if (0 == strcmp(p, "symbolic")) + return(MDOC_Symbolic); + break; + + case (MDOC_Bk): + if (0 == strcmp(p, "words")) + return(MDOC_Words); + break; + + case (MDOC_Bl): + if (0 == strcmp(p, "bullet")) + return(MDOC_Bullet); + else if (0 == strcmp(p, "dash")) + return(MDOC_Dash); + else if (0 == strcmp(p, "hyphen")) + return(MDOC_Hyphen); + else if (0 == strcmp(p, "item")) + return(MDOC_Item); + else if (0 == strcmp(p, "enum")) + return(MDOC_Enum); + else if (0 == strcmp(p, "tag")) + return(MDOC_Tag); + else if (0 == strcmp(p, "diag")) + return(MDOC_Diag); + else if (0 == strcmp(p, "hang")) + return(MDOC_Hang); + else if (0 == strcmp(p, "ohang")) + return(MDOC_Ohang); + else if (0 == strcmp(p, "inset")) + return(MDOC_Inset); + else if (0 == strcmp(p, "column")) + return(MDOC_Column); + else if (0 == strcmp(p, "width")) + return(MDOC_Width); + else if (0 == strcmp(p, "offset")) + return(MDOC_Offset); + else if (0 == strcmp(p, "compact")) + return(MDOC_Compact); + else if (0 == strcmp(p, "nested")) + return(MDOC_Nested); + break; + + case (MDOC_Rv): + /* FALLTHROUGH */ + case (MDOC_Ex): + if (0 == strcmp(p, "std")) + return(MDOC_Std); + break; + default: + break; + } + + return(MDOC_ARG_MAX); +} + + +static int +argv_multi(struct mdoc *m, int line, + struct mdoc_argv *v, int *pos, char *buf) +{ + int c; + char *p; + + for (v->sz = 0; ; v->sz++) { + if ('-' == buf[*pos]) + break; + c = args(m, line, pos, buf, 0, &p); + if (ARGS_ERROR == c) + return(0); + else if (ARGS_EOLN == c) + break; + + if (0 == v->sz % MULTI_STEP) { + v->value = realloc(v->value, + (v->sz + MULTI_STEP) * sizeof(char *)); + if (NULL == v->value) { + (void)mdoc_nerr(m, m->last, EMALLOC); + return(ARGV_ERROR); + } + } + if (NULL == (v->value[(int)v->sz] = strdup(p))) + return(mdoc_nerr(m, m->last, EMALLOC)); + } + + return(1); +} + + +static int +argv_opt_single(struct mdoc *m, int line, + struct mdoc_argv *v, int *pos, char *buf) +{ + int c; + char *p; + + if ('-' == buf[*pos]) + return(1); + + c = args(m, line, pos, buf, 0, &p); + if (ARGS_ERROR == c) + return(0); + if (ARGS_EOLN == c) + return(1); + + v->sz = 1; + if (NULL == (v->value = calloc(1, sizeof(char *)))) + return(mdoc_nerr(m, m->last, EMALLOC)); + if (NULL == (v->value[0] = strdup(p))) + return(mdoc_nerr(m, m->last, EMALLOC)); + + return(1); +} + + +/* + * Parse a single, mandatory value from the stream. + */ +static int +argv_single(struct mdoc *m, int line, + struct mdoc_argv *v, int *pos, char *buf) +{ + int c, ppos; + char *p; + + ppos = *pos; + + c = args(m, line, pos, buf, 0, &p); + if (ARGS_ERROR == c) + return(0); + if (ARGS_EOLN == c) + return(mdoc_perr(m, line, ppos, EARGVAL)); + + v->sz = 1; + if (NULL == (v->value = calloc(1, sizeof(char *)))) + return(mdoc_nerr(m, m->last, EMALLOC)); + if (NULL == (v->value[0] = strdup(p))) + return(mdoc_nerr(m, m->last, EMALLOC)); + + return(1); +} + + +/* + * Determine rules for parsing arguments. Arguments can either accept + * no parameters, an optional single parameter, one parameter, or + * multiple parameters. + */ +static int +argv(struct mdoc *mdoc, int line, + struct mdoc_argv *v, int *pos, char *buf) +{ + + v->sz = 0; + v->value = NULL; + + switch (mdoc_argvflags[v->arg]) { + case (ARGV_SINGLE): + return(argv_single(mdoc, line, v, pos, buf)); + case (ARGV_MULTI): + return(argv_multi(mdoc, line, v, pos, buf)); + case (ARGV_OPT_SINGLE): + return(argv_opt_single(mdoc, line, v, pos, buf)); + default: + /* ARGV_NONE */ + break; + } + + return(1); +} diff --git a/usr.bin/mandoc/mdoc_hash.c b/usr.bin/mandoc/mdoc_hash.c new file mode 100644 index 0000000000..8327f55b41 --- /dev/null +++ b/usr.bin/mandoc/mdoc_hash.c @@ -0,0 +1,88 @@ +/* $Id: mdoc_hash.c,v 1.6 2009/09/21 21:11:37 schwarze Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include + +#include +#include +#include +#include +#include +#include + +#include "libmdoc.h" + +static u_char table[27 * 12]; + +/* + * XXX - this hash has global scope, so if intended for use as a library + * with multiple callers, it will need re-invocation protection. + */ +void +mdoc_hash_init(void) +{ + int i, j, major; + const char *p; + + memset(table, UCHAR_MAX, sizeof(table)); + + for (i = 0; i < MDOC_MAX; i++) { + p = mdoc_macronames[i]; + + if (isalpha((u_char)p[1])) + major = 12 * (tolower((u_char)p[1]) - 97); + else + major = 12 * 26; + + for (j = 0; j < 12; j++) + if (UCHAR_MAX == table[major + j]) { + table[major + j] = (u_char)i; + break; + } + + assert(j < 12); + } +} + +int +mdoc_hash_find(const char *p) +{ + int major, i, j; + + if (0 == p[0]) + return(MDOC_MAX); + if ( ! isalpha((u_char)p[0]) && '%' != p[0]) + return(MDOC_MAX); + + if (isalpha((u_char)p[1])) + major = 12 * (tolower((u_char)p[1]) - 97); + else if ('1' == p[1]) + major = 12 * 26; + else + return(MDOC_MAX); + + if (p[2] && p[3]) + return(MDOC_MAX); + + for (j = 0; j < 12; j++) { + if (UCHAR_MAX == (i = table[major + j])) + break; + if (0 == strcmp(p, mdoc_macronames[i])) + return(i); + } + + return(MDOC_MAX); +} diff --git a/usr.bin/mandoc/mdoc_html.c b/usr.bin/mandoc/mdoc_html.c new file mode 100644 index 0000000000..e258afd47c --- /dev/null +++ b/usr.bin/mandoc/mdoc_html.c @@ -0,0 +1,2217 @@ +/* $Id: mdoc_html.c,v 1.1 2009/10/21 19:13:50 schwarze Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "out.h" +#include "html.h" +#include "mdoc.h" +#include "main.h" + +#define INDENT 5 +#define HALFINDENT 3 + +#define MDOC_ARGS const struct mdoc_meta *m, \ + const struct mdoc_node *n, \ + struct html *h + +struct htmlmdoc { + int (*pre)(MDOC_ARGS); + void (*post)(MDOC_ARGS); +}; + +static void print_mdoc(MDOC_ARGS); +static void print_mdoc_head(MDOC_ARGS); +static void print_mdoc_node(MDOC_ARGS); +static void print_mdoc_nodelist(MDOC_ARGS); + +static void a2width(const char *, struct roffsu *); +static void a2offs(const char *, struct roffsu *); + +static int a2list(const struct mdoc_node *); + +static void mdoc_root_post(MDOC_ARGS); +static int mdoc_root_pre(MDOC_ARGS); + +static void mdoc__x_post(MDOC_ARGS); +static int mdoc__x_pre(MDOC_ARGS); +static int mdoc_ad_pre(MDOC_ARGS); +static int mdoc_an_pre(MDOC_ARGS); +static int mdoc_ap_pre(MDOC_ARGS); +static void mdoc_aq_post(MDOC_ARGS); +static int mdoc_aq_pre(MDOC_ARGS); +static int mdoc_ar_pre(MDOC_ARGS); +static int mdoc_bd_pre(MDOC_ARGS); +static int mdoc_bf_pre(MDOC_ARGS); +static void mdoc_bl_post(MDOC_ARGS); +static int mdoc_bl_pre(MDOC_ARGS); +static void mdoc_bq_post(MDOC_ARGS); +static int mdoc_bq_pre(MDOC_ARGS); +static void mdoc_brq_post(MDOC_ARGS); +static int mdoc_brq_pre(MDOC_ARGS); +static int mdoc_bt_pre(MDOC_ARGS); +static int mdoc_bx_pre(MDOC_ARGS); +static int mdoc_cd_pre(MDOC_ARGS); +static int mdoc_d1_pre(MDOC_ARGS); +static void mdoc_dq_post(MDOC_ARGS); +static int mdoc_dq_pre(MDOC_ARGS); +static int mdoc_dv_pre(MDOC_ARGS); +static int mdoc_fa_pre(MDOC_ARGS); +static int mdoc_fd_pre(MDOC_ARGS); +static int mdoc_fl_pre(MDOC_ARGS); +static int mdoc_fn_pre(MDOC_ARGS); +static int mdoc_ft_pre(MDOC_ARGS); +static int mdoc_em_pre(MDOC_ARGS); +static int mdoc_er_pre(MDOC_ARGS); +static int mdoc_ev_pre(MDOC_ARGS); +static int mdoc_ex_pre(MDOC_ARGS); +static void mdoc_fo_post(MDOC_ARGS); +static int mdoc_fo_pre(MDOC_ARGS); +static int mdoc_ic_pre(MDOC_ARGS); +static int mdoc_in_pre(MDOC_ARGS); +static int mdoc_it_block_pre(MDOC_ARGS, int, int, + struct roffsu *, struct roffsu *); +static int mdoc_it_head_pre(MDOC_ARGS, int, + struct roffsu *); +static int mdoc_it_body_pre(MDOC_ARGS, int); +static int mdoc_it_pre(MDOC_ARGS); +static int mdoc_lb_pre(MDOC_ARGS); +static int mdoc_li_pre(MDOC_ARGS); +static int mdoc_lk_pre(MDOC_ARGS); +static int mdoc_mt_pre(MDOC_ARGS); +static int mdoc_ms_pre(MDOC_ARGS); +static int mdoc_nd_pre(MDOC_ARGS); +static int mdoc_nm_pre(MDOC_ARGS); +static int mdoc_ns_pre(MDOC_ARGS); +static void mdoc_op_post(MDOC_ARGS); +static int mdoc_op_pre(MDOC_ARGS); +static int mdoc_pa_pre(MDOC_ARGS); +static void mdoc_pf_post(MDOC_ARGS); +static int mdoc_pf_pre(MDOC_ARGS); +static void mdoc_pq_post(MDOC_ARGS); +static int mdoc_pq_pre(MDOC_ARGS); +static int mdoc_rs_pre(MDOC_ARGS); +static int mdoc_rv_pre(MDOC_ARGS); +static int mdoc_sh_pre(MDOC_ARGS); +static int mdoc_sp_pre(MDOC_ARGS); +static void mdoc_sq_post(MDOC_ARGS); +static int mdoc_sq_pre(MDOC_ARGS); +static int mdoc_ss_pre(MDOC_ARGS); +static int mdoc_sx_pre(MDOC_ARGS); +static int mdoc_sy_pre(MDOC_ARGS); +static int mdoc_ud_pre(MDOC_ARGS); +static int mdoc_va_pre(MDOC_ARGS); +static int mdoc_vt_pre(MDOC_ARGS); +static int mdoc_xr_pre(MDOC_ARGS); +static int mdoc_xx_pre(MDOC_ARGS); + +static const struct htmlmdoc mdocs[MDOC_MAX] = { + {mdoc_ap_pre, NULL}, /* Ap */ + {NULL, NULL}, /* Dd */ + {NULL, NULL}, /* Dt */ + {NULL, NULL}, /* Os */ + {mdoc_sh_pre, NULL }, /* Sh */ + {mdoc_ss_pre, NULL }, /* Ss */ + {mdoc_sp_pre, NULL}, /* Pp */ + {mdoc_d1_pre, NULL}, /* D1 */ + {mdoc_d1_pre, NULL}, /* Dl */ + {mdoc_bd_pre, NULL}, /* Bd */ + {NULL, NULL}, /* Ed */ + {mdoc_bl_pre, mdoc_bl_post}, /* Bl */ + {NULL, NULL}, /* El */ + {mdoc_it_pre, NULL}, /* It */ + {mdoc_ad_pre, NULL}, /* Ad */ + {mdoc_an_pre, NULL}, /* An */ + {mdoc_ar_pre, NULL}, /* Ar */ + {mdoc_cd_pre, NULL}, /* Cd */ + {mdoc_fl_pre, NULL}, /* Cm */ + {mdoc_dv_pre, NULL}, /* Dv */ + {mdoc_er_pre, NULL}, /* Er */ + {mdoc_ev_pre, NULL}, /* Ev */ + {mdoc_ex_pre, NULL}, /* Ex */ + {mdoc_fa_pre, NULL}, /* Fa */ + {mdoc_fd_pre, NULL}, /* Fd */ + {mdoc_fl_pre, NULL}, /* Fl */ + {mdoc_fn_pre, NULL}, /* Fn */ + {mdoc_ft_pre, NULL}, /* Ft */ + {mdoc_ic_pre, NULL}, /* Ic */ + {mdoc_in_pre, NULL}, /* In */ + {mdoc_li_pre, NULL}, /* Li */ + {mdoc_nd_pre, NULL}, /* Nd */ + {mdoc_nm_pre, NULL}, /* Nm */ + {mdoc_op_pre, mdoc_op_post}, /* Op */ + {NULL, NULL}, /* Ot */ + {mdoc_pa_pre, NULL}, /* Pa */ + {mdoc_rv_pre, NULL}, /* Rv */ + {NULL, NULL}, /* St */ + {mdoc_va_pre, NULL}, /* Va */ + {mdoc_vt_pre, NULL}, /* Vt */ + {mdoc_xr_pre, NULL}, /* Xr */ + {mdoc__x_pre, mdoc__x_post}, /* %A */ + {mdoc__x_pre, mdoc__x_post}, /* %B */ + {mdoc__x_pre, mdoc__x_post}, /* %D */ + {mdoc__x_pre, mdoc__x_post}, /* %I */ + {mdoc__x_pre, mdoc__x_post}, /* %J */ + {mdoc__x_pre, mdoc__x_post}, /* %N */ + {mdoc__x_pre, mdoc__x_post}, /* %O */ + {mdoc__x_pre, mdoc__x_post}, /* %P */ + {mdoc__x_pre, mdoc__x_post}, /* %R */ + {mdoc__x_pre, mdoc__x_post}, /* %T */ + {mdoc__x_pre, mdoc__x_post}, /* %V */ + {NULL, NULL}, /* Ac */ + {mdoc_aq_pre, mdoc_aq_post}, /* Ao */ + {mdoc_aq_pre, mdoc_aq_post}, /* Aq */ + {NULL, NULL}, /* At */ + {NULL, NULL}, /* Bc */ + {mdoc_bf_pre, NULL}, /* Bf */ + {mdoc_bq_pre, mdoc_bq_post}, /* Bo */ + {mdoc_bq_pre, mdoc_bq_post}, /* Bq */ + {mdoc_xx_pre, NULL}, /* Bsx */ + {mdoc_bx_pre, NULL}, /* Bx */ + {NULL, NULL}, /* Db */ + {NULL, NULL}, /* Dc */ + {mdoc_dq_pre, mdoc_dq_post}, /* Do */ + {mdoc_dq_pre, mdoc_dq_post}, /* Dq */ + {NULL, NULL}, /* Ec */ + {NULL, NULL}, /* Ef */ + {mdoc_em_pre, NULL}, /* Em */ + {NULL, NULL}, /* Eo */ + {mdoc_xx_pre, NULL}, /* Fx */ + {mdoc_ms_pre, NULL}, /* Ms */ /* FIXME: convert to symbol? */ + {NULL, NULL}, /* No */ + {mdoc_ns_pre, NULL}, /* Ns */ + {mdoc_xx_pre, NULL}, /* Nx */ + {mdoc_xx_pre, NULL}, /* Ox */ + {NULL, NULL}, /* Pc */ + {mdoc_pf_pre, mdoc_pf_post}, /* Pf */ + {mdoc_pq_pre, mdoc_pq_post}, /* Po */ + {mdoc_pq_pre, mdoc_pq_post}, /* Pq */ + {NULL, NULL}, /* Qc */ + {mdoc_sq_pre, mdoc_sq_post}, /* Ql */ + {mdoc_dq_pre, mdoc_dq_post}, /* Qo */ + {mdoc_dq_pre, mdoc_dq_post}, /* Qq */ + {NULL, NULL}, /* Re */ + {mdoc_rs_pre, NULL}, /* Rs */ + {NULL, NULL}, /* Sc */ + {mdoc_sq_pre, mdoc_sq_post}, /* So */ + {mdoc_sq_pre, mdoc_sq_post}, /* Sq */ + {NULL, NULL}, /* Sm */ /* FIXME - no idea. */ + {mdoc_sx_pre, NULL}, /* Sx */ + {mdoc_sy_pre, NULL}, /* Sy */ + {NULL, NULL}, /* Tn */ + {mdoc_xx_pre, NULL}, /* Ux */ + {NULL, NULL}, /* Xc */ + {NULL, NULL}, /* Xo */ + {mdoc_fo_pre, mdoc_fo_post}, /* Fo */ + {NULL, NULL}, /* Fc */ + {mdoc_op_pre, mdoc_op_post}, /* Oo */ + {NULL, NULL}, /* Oc */ + {NULL, NULL}, /* Bk */ + {NULL, NULL}, /* Ek */ + {mdoc_bt_pre, NULL}, /* Bt */ + {NULL, NULL}, /* Hf */ + {NULL, NULL}, /* Fr */ + {mdoc_ud_pre, NULL}, /* Ud */ + {mdoc_lb_pre, NULL}, /* Lb */ + {mdoc_sp_pre, NULL}, /* Lp */ + {mdoc_lk_pre, NULL}, /* Lk */ + {mdoc_mt_pre, NULL}, /* Mt */ + {mdoc_brq_pre, mdoc_brq_post}, /* Brq */ + {mdoc_brq_pre, mdoc_brq_post}, /* Bro */ + {NULL, NULL}, /* Brc */ + {mdoc__x_pre, mdoc__x_post}, /* %C */ + {NULL, NULL}, /* Es */ /* TODO */ + {NULL, NULL}, /* En */ /* TODO */ + {mdoc_xx_pre, NULL}, /* Dx */ + {mdoc__x_pre, mdoc__x_post}, /* %Q */ + {mdoc_sp_pre, NULL}, /* br */ + {mdoc_sp_pre, NULL}, /* sp */ +}; + + +void +html_mdoc(void *arg, const struct mdoc *m) +{ + struct html *h; + struct tag *t; + + h = (struct html *)arg; + + print_gen_doctype(h); + t = print_otag(h, TAG_HTML, 0, NULL); + print_mdoc(mdoc_meta(m), mdoc_node(m), h); + print_tagq(h, t); + + printf("\n"); +} + + +/* + * Return the list type for `Bl', e.g., `Bl -column' returns + * MDOC_Column. This can ONLY be run for lists; it will abort() if no + * list type is found. + */ +static int +a2list(const struct mdoc_node *n) +{ + int i; + + assert(n->args); + for (i = 0; i < (int)n->args->argc; i++) + switch (n->args->argv[i].arg) { + case (MDOC_Enum): + /* FALLTHROUGH */ + case (MDOC_Dash): + /* FALLTHROUGH */ + case (MDOC_Hyphen): + /* FALLTHROUGH */ + case (MDOC_Bullet): + /* FALLTHROUGH */ + case (MDOC_Tag): + /* FALLTHROUGH */ + case (MDOC_Hang): + /* FALLTHROUGH */ + case (MDOC_Inset): + /* FALLTHROUGH */ + case (MDOC_Diag): + /* FALLTHROUGH */ + case (MDOC_Item): + /* FALLTHROUGH */ + case (MDOC_Column): + /* FALLTHROUGH */ + case (MDOC_Ohang): + return(n->args->argv[i].arg); + default: + break; + } + + abort(); + /* NOTREACHED */ +} + + +/* + * Calculate the scaling unit passed in a `-width' argument. This uses + * either a native scaling unit (e.g., 1i, 2m) or the string length of + * the value. + */ +static void +a2width(const char *p, struct roffsu *su) +{ + + if ( ! a2roffsu(p, su, SCALE_MAX)) { + su->unit = SCALE_EM; + su->scale = (int)strlen(p); + } +} + + +/* + * Calculate the scaling unit passed in an `-offset' argument. This + * uses either a native scaling unit (e.g., 1i, 2m), one of a set of + * predefined strings (indent, etc.), or the string length of the value. + */ +static void +a2offs(const char *p, struct roffsu *su) +{ + + /* FIXME: "right"? */ + + if (0 == strcmp(p, "left")) + SCALE_HS_INIT(su, 0); + else if (0 == strcmp(p, "indent")) + SCALE_HS_INIT(su, INDENT); + else if (0 == strcmp(p, "indent-two")) + SCALE_HS_INIT(su, INDENT * 2); + else if ( ! a2roffsu(p, su, SCALE_MAX)) { + su->unit = SCALE_EM; + su->scale = (int)strlen(p); + } +} + + +static void +print_mdoc(MDOC_ARGS) +{ + struct tag *t; + struct htmlpair tag; + + t = print_otag(h, TAG_HEAD, 0, NULL); + print_mdoc_head(m, n, h); + print_tagq(h, t); + + t = print_otag(h, TAG_BODY, 0, NULL); + + tag.key = ATTR_CLASS; + tag.val = "body"; + print_otag(h, TAG_DIV, 1, &tag); + + print_mdoc_nodelist(m, n, h); + print_tagq(h, t); +} + + +/* ARGSUSED */ +static void +print_mdoc_head(MDOC_ARGS) +{ + + print_gen_head(h); + bufinit(h); + buffmt(h, "%s(%d)", m->title, m->msec); + + if (m->arch) { + bufcat(h, " ("); + bufcat(h, m->arch); + bufcat(h, ")"); + } + + print_otag(h, TAG_TITLE, 0, NULL); + print_text(h, h->buf); +} + + +static void +print_mdoc_nodelist(MDOC_ARGS) +{ + + print_mdoc_node(m, n, h); + if (n->next) + print_mdoc_nodelist(m, n->next, h); +} + + +static void +print_mdoc_node(MDOC_ARGS) +{ + int child; + struct tag *t; + + child = 1; + t = SLIST_FIRST(&h->tags); + + bufinit(h); + switch (n->type) { + case (MDOC_ROOT): + child = mdoc_root_pre(m, n, h); + break; + case (MDOC_TEXT): + print_text(h, n->string); + break; + default: + if (mdocs[n->tok].pre) + child = (*mdocs[n->tok].pre)(m, n, h); + break; + } + + if (child && n->child) + print_mdoc_nodelist(m, n->child, h); + + print_stagq(h, t); + + bufinit(h); + switch (n->type) { + case (MDOC_ROOT): + mdoc_root_post(m, n, h); + break; + case (MDOC_TEXT): + break; + default: + if (mdocs[n->tok].post) + (*mdocs[n->tok].post)(m, n, h); + break; + } +} + + +/* ARGSUSED */ +static void +mdoc_root_post(MDOC_ARGS) +{ + struct tm tm; + struct htmlpair tag[2]; + struct tag *t, *tt; + char b[BUFSIZ]; + + /* + * XXX: this should use divs, but in Firefox, divs with nested + * divs for some reason puke when trying to put a border line + * below. So I use tables, instead. + */ + + (void)localtime_r(&m->date, &tm); + + if (0 == strftime(b, BUFSIZ - 1, "%B %e, %Y", &tm)) + err(EXIT_FAILURE, "strftime"); + + PAIR_CLASS_INIT(&tag[0], "footer"); + bufcat_style(h, "width", "100%"); + PAIR_STYLE_INIT(&tag[1], h); + t = print_otag(h, TAG_TABLE, 2, tag); + tt = print_otag(h, TAG_TR, 0, NULL); + + bufinit(h); + bufcat_style(h, "width", "50%"); + PAIR_STYLE_INIT(&tag[0], h); + print_otag(h, TAG_TD, 1, tag); + print_text(h, b); + print_stagq(h, tt); + + bufinit(h); + bufcat_style(h, "width", "50%"); + bufcat_style(h, "text-align", "right"); + PAIR_STYLE_INIT(&tag[0], h); + print_otag(h, TAG_TD, 1, tag); + print_text(h, m->os); + print_tagq(h, t); +} + + +/* ARGSUSED */ +static int +mdoc_root_pre(MDOC_ARGS) +{ + struct htmlpair tag[2]; + struct tag *t, *tt; + char b[BUFSIZ], title[BUFSIZ]; + + (void)strlcpy(b, m->vol, BUFSIZ); + + if (m->arch) { + (void)strlcat(b, " (", BUFSIZ); + (void)strlcat(b, m->arch, BUFSIZ); + (void)strlcat(b, ")", BUFSIZ); + } + + (void)snprintf(title, BUFSIZ - 1, + "%s(%d)", m->title, m->msec); + + /* XXX: see note in mdoc_root_post() about divs. */ + + PAIR_CLASS_INIT(&tag[0], "header"); + bufcat_style(h, "width", "100%"); + PAIR_STYLE_INIT(&tag[1], h); + t = print_otag(h, TAG_TABLE, 2, tag); + tt = print_otag(h, TAG_TR, 0, NULL); + + bufinit(h); + bufcat_style(h, "width", "10%"); + PAIR_STYLE_INIT(&tag[0], h); + print_otag(h, TAG_TD, 1, tag); + print_text(h, title); + print_stagq(h, tt); + + bufinit(h); + bufcat_style(h, "text-align", "center"); + bufcat_style(h, "white-space", "nowrap"); + bufcat_style(h, "width", "80%"); + PAIR_STYLE_INIT(&tag[0], h); + print_otag(h, TAG_TD, 1, tag); + print_text(h, b); + print_stagq(h, tt); + + bufinit(h); + bufcat_style(h, "text-align", "right"); + bufcat_style(h, "width", "10%"); + PAIR_STYLE_INIT(&tag[0], h); + print_otag(h, TAG_TD, 1, tag); + print_text(h, title); + print_tagq(h, t); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_sh_pre(MDOC_ARGS) +{ + struct htmlpair tag[2]; + const struct mdoc_node *nn; + char lbuf[BUFSIZ]; + struct roffsu su; + + if (MDOC_BODY == n->type) { + SCALE_HS_INIT(&su, INDENT); + bufcat_su(h, "margin-left", &su); + PAIR_CLASS_INIT(&tag[0], "sec-body"); + PAIR_STYLE_INIT(&tag[1], h); + print_otag(h, TAG_DIV, 2, tag); + return(1); + } else if (MDOC_BLOCK == n->type) { + PAIR_CLASS_INIT(&tag[0], "sec-block"); + if (n->prev && NULL == n->prev->body->child) { + print_otag(h, TAG_DIV, 1, tag); + return(1); + } + + SCALE_VS_INIT(&su, 1); + bufcat_su(h, "margin-top", &su); + if (NULL == n->next) + bufcat_su(h, "margin-bottom", &su); + + PAIR_STYLE_INIT(&tag[1], h); + print_otag(h, TAG_DIV, 2, tag); + return(1); + } + + lbuf[0] = 0; + for (nn = n->child; nn; nn = nn->next) { + (void)strlcat(lbuf, nn->string, BUFSIZ); + if (nn->next) + (void)strlcat(lbuf, "_", BUFSIZ); + } + + /* + * TODO: make sure there are no duplicates, as HTML does not + * allow for multiple `id' tags of the same name. + */ + + PAIR_CLASS_INIT(&tag[0], "sec-head"); + tag[1].key = ATTR_ID; + tag[1].val = lbuf; + print_otag(h, TAG_DIV, 2, tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_ss_pre(MDOC_ARGS) +{ + struct htmlpair tag[3]; + const struct mdoc_node *nn; + char lbuf[BUFSIZ]; + struct roffsu su; + + SCALE_VS_INIT(&su, 1); + + if (MDOC_BODY == n->type) { + PAIR_CLASS_INIT(&tag[0], "ssec-body"); + if (n->parent->next && n->child) { + bufcat_su(h, "margin-bottom", &su); + PAIR_STYLE_INIT(&tag[1], h); + print_otag(h, TAG_DIV, 2, tag); + } else + print_otag(h, TAG_DIV, 1, tag); + return(1); + } else if (MDOC_BLOCK == n->type) { + PAIR_CLASS_INIT(&tag[0], "ssec-block"); + if (n->prev) { + bufcat_su(h, "margin-top", &su); + PAIR_STYLE_INIT(&tag[1], h); + print_otag(h, TAG_DIV, 2, tag); + } else + print_otag(h, TAG_DIV, 1, tag); + return(1); + } + + /* TODO: see note in mdoc_sh_pre() about duplicates. */ + + lbuf[0] = 0; + for (nn = n->child; nn; nn = nn->next) { + (void)strlcat(lbuf, nn->string, BUFSIZ); + if (nn->next) + (void)strlcat(lbuf, "_", BUFSIZ); + } + + SCALE_HS_INIT(&su, INDENT - HALFINDENT); + su.scale = -su.scale; + bufcat_su(h, "margin-left", &su); + + PAIR_CLASS_INIT(&tag[0], "ssec-head"); + PAIR_STYLE_INIT(&tag[1], h); + tag[2].key = ATTR_ID; + tag[2].val = lbuf; + print_otag(h, TAG_DIV, 3, tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_fl_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "flag"); + print_otag(h, TAG_SPAN, 1, &tag); + if (MDOC_Fl == n->tok) { + print_text(h, "\\-"); + h->flags |= HTML_NOSPACE; + } + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_nd_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + if (MDOC_BODY != n->type) + return(1); + + /* XXX: this tag in theory can contain block elements. */ + + print_text(h, "\\(em"); + PAIR_CLASS_INIT(&tag, "desc-body"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_op_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + if (MDOC_BODY != n->type) + return(1); + + /* XXX: this tag in theory can contain block elements. */ + + print_text(h, "\\(lB"); + h->flags |= HTML_NOSPACE; + PAIR_CLASS_INIT(&tag, "opt"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static void +mdoc_op_post(MDOC_ARGS) +{ + + if (MDOC_BODY != n->type) + return; + h->flags |= HTML_NOSPACE; + print_text(h, "\\(rB"); +} + + +static int +mdoc_nm_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + if ( ! (HTML_NEWLINE & h->flags)) + if (SEC_SYNOPSIS == n->sec) { + bufcat_style(h, "clear", "both"); + PAIR_STYLE_INIT(&tag, h); + print_otag(h, TAG_BR, 1, &tag); + } + + PAIR_CLASS_INIT(&tag, "name"); + print_otag(h, TAG_SPAN, 1, &tag); + if (NULL == n->child) + print_text(h, m->name); + + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_xr_pre(MDOC_ARGS) +{ + struct htmlpair tag[2]; + const struct mdoc_node *nn; + + PAIR_CLASS_INIT(&tag[0], "link-man"); + + if (h->base_man) { + buffmt_man(h, n->child->string, + n->child->next ? + n->child->next->string : NULL); + tag[1].key = ATTR_HREF; + tag[1].val = h->buf; + print_otag(h, TAG_A, 2, tag); + } else + print_otag(h, TAG_A, 1, tag); + + nn = n->child; + print_text(h, nn->string); + + if (NULL == (nn = nn->next)) + return(0); + + h->flags |= HTML_NOSPACE; + print_text(h, "("); + h->flags |= HTML_NOSPACE; + print_text(h, nn->string); + h->flags |= HTML_NOSPACE; + print_text(h, ")"); + return(0); +} + + +/* ARGSUSED */ +static int +mdoc_ns_pre(MDOC_ARGS) +{ + + h->flags |= HTML_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_ar_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "arg"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_xx_pre(MDOC_ARGS) +{ + const char *pp; + struct htmlpair tag; + + switch (n->tok) { + case (MDOC_Bsx): + pp = "BSDI BSD/OS"; + break; + case (MDOC_Dx): + pp = "DragonFlyBSD"; + break; + case (MDOC_Fx): + pp = "FreeBSD"; + break; + case (MDOC_Nx): + pp = "NetBSD"; + break; + case (MDOC_Ox): + pp = "OpenBSD"; + break; + case (MDOC_Ux): + pp = "UNIX"; + break; + default: + return(1); + } + + PAIR_CLASS_INIT(&tag, "unix"); + print_otag(h, TAG_SPAN, 1, &tag); + print_text(h, pp); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_bx_pre(MDOC_ARGS) +{ + const struct mdoc_node *nn; + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "unix"); + print_otag(h, TAG_SPAN, 1, &tag); + + for (nn = n->child; nn; nn = nn->next) + print_mdoc_node(m, nn, h); + + if (n->child) + h->flags |= HTML_NOSPACE; + + print_text(h, "BSD"); + return(0); +} + + +/* ARGSUSED */ +static int +mdoc_it_block_pre(MDOC_ARGS, int type, int comp, + struct roffsu *offs, struct roffsu *width) +{ + struct htmlpair tag; + const struct mdoc_node *nn; + struct roffsu su; + + nn = n->parent->parent; + assert(nn->args); + + /* XXX: see notes in mdoc_it_pre(). */ + + if (MDOC_Column == type) { + /* Don't width-pad on the left. */ + SCALE_HS_INIT(width, 0); + /* Also disallow non-compact. */ + comp = 1; + } + if (MDOC_Diag == type) + /* Mandate non-compact with empty prior. */ + if (n->prev && NULL == n->prev->body->child) + comp = 1; + + bufcat_style(h, "clear", "both"); + if (offs->scale > 0) + bufcat_su(h, "margin-left", offs); + if (width->scale > 0) + bufcat_su(h, "padding-left", width); + + PAIR_STYLE_INIT(&tag, h); + + /* Mandate compact following `Ss' and `Sh' starts. */ + + for (nn = n; nn && ! comp; nn = nn->parent) { + if (MDOC_BLOCK != nn->type) + continue; + if (MDOC_Ss == nn->tok || MDOC_Sh == nn->tok) + comp = 1; + if (nn->prev) + break; + } + + if ( ! comp) { + SCALE_VS_INIT(&su, 1); + bufcat_su(h, "padding-top", &su); + } + + PAIR_STYLE_INIT(&tag, h); + print_otag(h, TAG_DIV, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_it_body_pre(MDOC_ARGS, int type) +{ + struct htmlpair tag; + struct roffsu su; + + switch (type) { + case (MDOC_Item): + /* FALLTHROUGH */ + case (MDOC_Ohang): + /* FALLTHROUGH */ + case (MDOC_Column): + break; + default: + /* + * XXX: this tricks CSS into aligning the bodies with + * the right-padding in the head. + */ + SCALE_HS_INIT(&su, 2); + bufcat_su(h, "margin-left", &su); + PAIR_STYLE_INIT(&tag, h); + print_otag(h, TAG_DIV, 1, &tag); + break; + } + + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_it_head_pre(MDOC_ARGS, int type, struct roffsu *width) +{ + struct htmlpair tag; + struct ord *ord; + char nbuf[BUFSIZ]; + + switch (type) { + case (MDOC_Item): + /* FALLTHROUGH */ + case (MDOC_Ohang): + print_otag(h, TAG_DIV, 0, NULL); + break; + case (MDOC_Column): + bufcat_su(h, "min-width", width); + bufcat_style(h, "clear", "none"); + if (n->next && MDOC_HEAD == n->next->type) + bufcat_style(h, "float", "left"); + PAIR_STYLE_INIT(&tag, h); + print_otag(h, TAG_DIV, 1, &tag); + break; + default: + bufcat_su(h, "min-width", width); + SCALE_INVERT(width); + bufcat_su(h, "margin-left", width); + if (n->next && n->next->child) + bufcat_style(h, "float", "left"); + + /* XXX: buffer if we run into body. */ + SCALE_HS_INIT(width, 1); + bufcat_su(h, "margin-right", width); + PAIR_STYLE_INIT(&tag, h); + print_otag(h, TAG_DIV, 1, &tag); + break; + } + + switch (type) { + case (MDOC_Diag): + PAIR_CLASS_INIT(&tag, "diag"); + print_otag(h, TAG_SPAN, 1, &tag); + break; + case (MDOC_Enum): + ord = SLIST_FIRST(&h->ords); + assert(ord); + nbuf[BUFSIZ - 1] = 0; + (void)snprintf(nbuf, BUFSIZ - 1, "%d.", ord->pos++); + print_text(h, nbuf); + return(0); + case (MDOC_Dash): + print_text(h, "\\(en"); + return(0); + case (MDOC_Hyphen): + print_text(h, "\\(hy"); + return(0); + case (MDOC_Bullet): + print_text(h, "\\(bu"); + return(0); + default: + break; + } + + return(1); +} + + +static int +mdoc_it_pre(MDOC_ARGS) +{ + int i, type, wp, comp; + const struct mdoc_node *bl, *nn; + struct roffsu width, offs; + + /* + * XXX: be very careful in changing anything, here. Lists in + * mandoc have many peculiarities; furthermore, they don't + * translate well into HTML and require a bit of mangling. + */ + + bl = n->parent->parent; + if (MDOC_BLOCK != n->type) + bl = bl->parent; + + type = a2list(bl); + + /* Set default width and offset. */ + + SCALE_HS_INIT(&offs, 0); + + switch (type) { + case (MDOC_Enum): + /* FALLTHROUGH */ + case (MDOC_Dash): + /* FALLTHROUGH */ + case (MDOC_Hyphen): + /* FALLTHROUGH */ + case (MDOC_Bullet): + SCALE_HS_INIT(&width, 2); + break; + default: + SCALE_HS_INIT(&width, INDENT); + break; + } + + /* Get width, offset, and compact arguments. */ + + for (wp = -1, comp = i = 0; i < (int)bl->args->argc; i++) + switch (bl->args->argv[i].arg) { + case (MDOC_Column): + wp = i; /* Save for later. */ + break; + case (MDOC_Width): + a2width(bl->args->argv[i].value[0], &width); + break; + case (MDOC_Offset): + a2offs(bl->args->argv[i].value[0], &offs); + break; + case (MDOC_Compact): + comp = 1; + break; + default: + break; + } + + /* Override width in some cases. */ + + switch (type) { + case (MDOC_Item): + /* FALLTHROUGH */ + case (MDOC_Inset): + /* FALLTHROUGH */ + case (MDOC_Diag): + SCALE_HS_INIT(&width, 0); + break; + default: + if (0 == width.scale) + SCALE_HS_INIT(&width, INDENT); + break; + } + + /* Flip to body/block processing. */ + + if (MDOC_BODY == n->type) + return(mdoc_it_body_pre(m, n, h, type)); + if (MDOC_BLOCK == n->type) + return(mdoc_it_block_pre(m, n, h, type, comp, + &offs, &width)); + + /* Override column widths. */ + + if (MDOC_Column == type) { + nn = n->parent->child; + for (i = 0; nn && nn != n; nn = nn->next, i++) + /* Counter... */ ; + if (i < (int)bl->args->argv[wp].sz) + a2width(bl->args->argv[wp].value[i], &width); + } + + return(mdoc_it_head_pre(m, n, h, type, &width)); +} + + +/* ARGSUSED */ +static int +mdoc_bl_pre(MDOC_ARGS) +{ + struct ord *ord; + + if (MDOC_BLOCK != n->type) + return(1); + if (MDOC_Enum != a2list(n)) + return(1); + + ord = malloc(sizeof(struct ord)); + if (NULL == ord) + err(EXIT_FAILURE, "malloc"); + ord->cookie = n; + ord->pos = 1; + SLIST_INSERT_HEAD(&h->ords, ord, entry); + return(1); +} + + +/* ARGSUSED */ +static void +mdoc_bl_post(MDOC_ARGS) +{ + struct ord *ord; + + if (MDOC_BLOCK != n->type) + return; + if (MDOC_Enum != a2list(n)) + return; + + ord = SLIST_FIRST(&h->ords); + assert(ord); + SLIST_REMOVE_HEAD(&h->ords, entry); + free(ord); +} + + +/* ARGSUSED */ +static int +mdoc_ex_pre(MDOC_ARGS) +{ + const struct mdoc_node *nn; + struct tag *t; + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "utility"); + + print_text(h, "The"); + for (nn = n->child; nn; nn = nn->next) { + t = print_otag(h, TAG_SPAN, 1, &tag); + print_text(h, nn->string); + print_tagq(h, t); + + h->flags |= HTML_NOSPACE; + + if (nn->next && NULL == nn->next->next) + print_text(h, ", and"); + else if (nn->next) + print_text(h, ","); + else + h->flags &= ~HTML_NOSPACE; + } + + if (n->child->next) + print_text(h, "utilities exit"); + else + print_text(h, "utility exits"); + + print_text(h, "0 on success, and >0 if an error occurs."); + return(0); +} + + +/* ARGSUSED */ +static int +mdoc_dq_pre(MDOC_ARGS) +{ + + if (MDOC_BODY != n->type) + return(1); + print_text(h, "\\(lq"); + h->flags |= HTML_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static void +mdoc_dq_post(MDOC_ARGS) +{ + + if (MDOC_BODY != n->type) + return; + h->flags |= HTML_NOSPACE; + print_text(h, "\\(rq"); +} + + +/* ARGSUSED */ +static int +mdoc_pq_pre(MDOC_ARGS) +{ + + if (MDOC_BODY != n->type) + return(1); + print_text(h, "\\&("); + h->flags |= HTML_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static void +mdoc_pq_post(MDOC_ARGS) +{ + + if (MDOC_BODY != n->type) + return; + print_text(h, ")"); +} + + +/* ARGSUSED */ +static int +mdoc_sq_pre(MDOC_ARGS) +{ + + if (MDOC_BODY != n->type) + return(1); + print_text(h, "\\(oq"); + h->flags |= HTML_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static void +mdoc_sq_post(MDOC_ARGS) +{ + + if (MDOC_BODY != n->type) + return; + h->flags |= HTML_NOSPACE; + print_text(h, "\\(aq"); +} + + +/* ARGSUSED */ +static int +mdoc_em_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "emph"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_d1_pre(MDOC_ARGS) +{ + struct htmlpair tag[2]; + struct roffsu su; + + if (MDOC_BLOCK != n->type) + return(1); + + /* FIXME: D1 shouldn't be literal. */ + + SCALE_VS_INIT(&su, INDENT - 2); + bufcat_su(h, "margin-left", &su); + PAIR_CLASS_INIT(&tag[0], "lit"); + PAIR_STYLE_INIT(&tag[1], h); + print_otag(h, TAG_DIV, 2, tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_sx_pre(MDOC_ARGS) +{ + struct htmlpair tag[2]; + const struct mdoc_node *nn; + char buf[BUFSIZ]; + + /* FIXME: duplicates? */ + + (void)strlcpy(buf, "#", BUFSIZ); + for (nn = n->child; nn; nn = nn->next) { + (void)strlcat(buf, nn->string, BUFSIZ); + if (nn->next) + (void)strlcat(buf, "_", BUFSIZ); + } + + PAIR_CLASS_INIT(&tag[0], "link-sec"); + tag[1].key = ATTR_HREF; + tag[1].val = buf; + + print_otag(h, TAG_A, 2, tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_aq_pre(MDOC_ARGS) +{ + + if (MDOC_BODY != n->type) + return(1); + print_text(h, "\\(la"); + h->flags |= HTML_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static void +mdoc_aq_post(MDOC_ARGS) +{ + + if (MDOC_BODY != n->type) + return; + h->flags |= HTML_NOSPACE; + print_text(h, "\\(ra"); +} + + +/* ARGSUSED */ +static int +mdoc_bd_pre(MDOC_ARGS) +{ + struct htmlpair tag[2]; + int type, comp, i; + const struct mdoc_node *bl, *nn; + struct roffsu su; + + if (MDOC_BLOCK == n->type) + bl = n; + else if (MDOC_HEAD == n->type) + return(0); + else + bl = n->parent; + + SCALE_VS_INIT(&su, 0); + + type = comp = 0; + for (i = 0; i < (int)bl->args->argc; i++) + switch (bl->args->argv[i].arg) { + case (MDOC_Offset): + a2offs(bl->args->argv[i].value[0], &su); + break; + case (MDOC_Compact): + comp = 1; + break; + case (MDOC_Centred): + /* FALLTHROUGH */ + case (MDOC_Ragged): + /* FALLTHROUGH */ + case (MDOC_Filled): + /* FALLTHROUGH */ + case (MDOC_Unfilled): + /* FALLTHROUGH */ + case (MDOC_Literal): + type = bl->args->argv[i].arg; + break; + default: + break; + } + + /* FIXME: -centered, etc. formatting. */ + + if (MDOC_BLOCK == n->type) { + bufcat_su(h, "margin-left", &su); + for (nn = n; nn && ! comp; nn = nn->parent) { + if (MDOC_BLOCK != nn->type) + continue; + if (MDOC_Ss == nn->tok || MDOC_Sh == nn->tok) + comp = 1; + if (nn->prev) + break; + } + if (comp) { + print_otag(h, TAG_DIV, 0, tag); + return(1); + } + SCALE_VS_INIT(&su, 1); + bufcat_su(h, "margin-top", &su); + PAIR_STYLE_INIT(&tag[0], h); + print_otag(h, TAG_DIV, 1, tag); + return(1); + } + + if (MDOC_Unfilled != type && MDOC_Literal != type) + return(1); + + PAIR_CLASS_INIT(&tag[0], "lit"); + bufcat_style(h, "white-space", "pre"); + PAIR_STYLE_INIT(&tag[1], h); + print_otag(h, TAG_DIV, 2, tag); + + for (nn = n->child; nn; nn = nn->next) { + h->flags |= HTML_NOSPACE; + print_mdoc_node(m, nn, h); + if (NULL == nn->next) + continue; + if (nn->prev && nn->prev->line < nn->line) + print_text(h, "\n"); + else if (NULL == nn->prev) + print_text(h, "\n"); + } + + return(0); +} + + +/* ARGSUSED */ +static int +mdoc_pa_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "file"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_ad_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "addr"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_an_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + /* TODO: -split and -nosplit (see termp_an_pre()). */ + + PAIR_CLASS_INIT(&tag, "author"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_cd_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + print_otag(h, TAG_DIV, 0, NULL); + PAIR_CLASS_INIT(&tag, "config"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_dv_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "define"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_ev_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "env"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_er_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "errno"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_fa_pre(MDOC_ARGS) +{ + const struct mdoc_node *nn; + struct htmlpair tag; + struct tag *t; + + PAIR_CLASS_INIT(&tag, "farg"); + if (n->parent->tok != MDOC_Fo) { + print_otag(h, TAG_SPAN, 1, &tag); + return(1); + } + + for (nn = n->child; nn; nn = nn->next) { + t = print_otag(h, TAG_SPAN, 1, &tag); + print_text(h, nn->string); + print_tagq(h, t); + if (nn->next) + print_text(h, ","); + } + + if (n->child && n->next && n->next->tok == MDOC_Fa) + print_text(h, ","); + + return(0); +} + + +/* ARGSUSED */ +static int +mdoc_fd_pre(MDOC_ARGS) +{ + struct htmlpair tag; + struct roffsu su; + + if (SEC_SYNOPSIS == n->sec) { + if (n->next && MDOC_Fd != n->next->tok) { + SCALE_VS_INIT(&su, 1); + bufcat_su(h, "margin-bottom", &su); + PAIR_STYLE_INIT(&tag, h); + print_otag(h, TAG_DIV, 1, &tag); + } else + print_otag(h, TAG_DIV, 0, NULL); + } + + PAIR_CLASS_INIT(&tag, "macro"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_vt_pre(MDOC_ARGS) +{ + struct htmlpair tag; + struct roffsu su; + + if (SEC_SYNOPSIS == n->sec) { + if (n->next && MDOC_Vt != n->next->tok) { + SCALE_VS_INIT(&su, 1); + bufcat_su(h, "margin-bottom", &su); + PAIR_STYLE_INIT(&tag, h); + print_otag(h, TAG_DIV, 1, &tag); + } else + print_otag(h, TAG_DIV, 0, NULL); + } + + PAIR_CLASS_INIT(&tag, "type"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_ft_pre(MDOC_ARGS) +{ + struct htmlpair tag; + struct roffsu su; + + if (SEC_SYNOPSIS == n->sec) { + if (n->prev && MDOC_Fo == n->prev->tok) { + SCALE_VS_INIT(&su, 1); + bufcat_su(h, "margin-top", &su); + PAIR_STYLE_INIT(&tag, h); + print_otag(h, TAG_DIV, 1, &tag); + } else + print_otag(h, TAG_DIV, 0, NULL); + } + + PAIR_CLASS_INIT(&tag, "ftype"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_fn_pre(MDOC_ARGS) +{ + struct tag *t; + struct htmlpair tag[2]; + const struct mdoc_node *nn; + char nbuf[BUFSIZ]; + const char *sp, *ep; + int sz, i; + struct roffsu su; + + if (SEC_SYNOPSIS == n->sec) { + SCALE_HS_INIT(&su, INDENT); + bufcat_su(h, "margin-left", &su); + su.scale = -su.scale; + bufcat_su(h, "text-indent", &su); + if (n->next) { + SCALE_VS_INIT(&su, 1); + bufcat_su(h, "margin-bottom", &su); + } + PAIR_STYLE_INIT(&tag[0], h); + print_otag(h, TAG_DIV, 1, tag); + } + + /* Split apart into type and name. */ + assert(n->child->string); + sp = n->child->string; + + ep = strchr(sp, ' '); + if (NULL != ep) { + PAIR_CLASS_INIT(&tag[0], "ftype"); + t = print_otag(h, TAG_SPAN, 1, tag); + + while (ep) { + sz = MIN((int)(ep - sp), BUFSIZ - 1); + (void)memcpy(nbuf, sp, (size_t)sz); + nbuf[sz] = '\0'; + print_text(h, nbuf); + sp = ++ep; + ep = strchr(sp, ' '); + } + print_tagq(h, t); + } + + PAIR_CLASS_INIT(&tag[0], "fname"); + t = print_otag(h, TAG_SPAN, 1, tag); + + if (sp) { + (void)strlcpy(nbuf, sp, BUFSIZ); + print_text(h, nbuf); + } + + print_tagq(h, t); + + h->flags |= HTML_NOSPACE; + print_text(h, "("); + + bufinit(h); + PAIR_CLASS_INIT(&tag[0], "farg"); + bufcat_style(h, "white-space", "nowrap"); + PAIR_STYLE_INIT(&tag[1], h); + + for (nn = n->child->next; nn; nn = nn->next) { + i = 1; + if (SEC_SYNOPSIS == n->sec) + i = 2; + t = print_otag(h, TAG_SPAN, i, tag); + print_text(h, nn->string); + print_tagq(h, t); + if (nn->next) + print_text(h, ","); + } + + print_text(h, ")"); + if (SEC_SYNOPSIS == n->sec) + print_text(h, ";"); + + return(0); +} + + +/* ARGSUSED */ +static int +mdoc_sp_pre(MDOC_ARGS) +{ + int len; + struct htmlpair tag; + struct roffsu su; + + switch (n->tok) { + case (MDOC_sp): + /* FIXME: can this have a scaling indicator? */ + len = n->child ? atoi(n->child->string) : 1; + break; + case (MDOC_br): + len = 0; + break; + default: + len = 1; + break; + } + + SCALE_VS_INIT(&su, len); + bufcat_su(h, "height", &su); + PAIR_STYLE_INIT(&tag, h); + print_otag(h, TAG_DIV, 1, &tag); + return(1); + +} + + +/* ARGSUSED */ +static int +mdoc_brq_pre(MDOC_ARGS) +{ + + if (MDOC_BODY != n->type) + return(1); + print_text(h, "\\(lC"); + h->flags |= HTML_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static void +mdoc_brq_post(MDOC_ARGS) +{ + + if (MDOC_BODY != n->type) + return; + h->flags |= HTML_NOSPACE; + print_text(h, "\\(rC"); +} + + +/* ARGSUSED */ +static int +mdoc_lk_pre(MDOC_ARGS) +{ + const struct mdoc_node *nn; + struct htmlpair tag[2]; + + nn = n->child; + + PAIR_CLASS_INIT(&tag[0], "link-ext"); + tag[1].key = ATTR_HREF; + tag[1].val = nn->string; + print_otag(h, TAG_A, 2, tag); + + for (nn = nn->next; nn; nn = nn->next) + print_text(h, nn->string); + + return(0); +} + + +/* ARGSUSED */ +static int +mdoc_mt_pre(MDOC_ARGS) +{ + struct htmlpair tag[2]; + struct tag *t; + const struct mdoc_node *nn; + + PAIR_CLASS_INIT(&tag[0], "link-mail"); + + for (nn = n->child; nn; nn = nn->next) { + bufinit(h); + bufcat(h, "mailto:"); + bufcat(h, nn->string); + PAIR_STYLE_INIT(&tag[1], h); + t = print_otag(h, TAG_A, 2, tag); + print_text(h, nn->string); + print_tagq(h, t); + } + + return(0); +} + + +/* ARGSUSED */ +static int +mdoc_fo_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + if (MDOC_BODY == n->type) { + h->flags |= HTML_NOSPACE; + print_text(h, "("); + h->flags |= HTML_NOSPACE; + return(1); + } else if (MDOC_BLOCK == n->type) + return(1); + + PAIR_CLASS_INIT(&tag, "fname"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static void +mdoc_fo_post(MDOC_ARGS) +{ + if (MDOC_BODY != n->type) + return; + h->flags |= HTML_NOSPACE; + print_text(h, ")"); + h->flags |= HTML_NOSPACE; + print_text(h, ";"); +} + + +/* ARGSUSED */ +static int +mdoc_in_pre(MDOC_ARGS) +{ + const struct mdoc_node *nn; + struct tag *t; + struct htmlpair tag[2]; + int i; + struct roffsu su; + + if (SEC_SYNOPSIS == n->sec) { + if (n->next && MDOC_In != n->next->tok) { + SCALE_VS_INIT(&su, 1); + bufcat_su(h, "margin-bottom", &su); + PAIR_STYLE_INIT(&tag[0], h); + print_otag(h, TAG_DIV, 1, tag); + } else + print_otag(h, TAG_DIV, 0, NULL); + } + + /* FIXME: there's a buffer bug in here somewhere. */ + + PAIR_CLASS_INIT(&tag[0], "includes"); + print_otag(h, TAG_SPAN, 1, tag); + + if (SEC_SYNOPSIS == n->sec) + print_text(h, "#include"); + + print_text(h, "<"); + h->flags |= HTML_NOSPACE; + + /* XXX -- see warning in termp_in_post(). */ + + for (nn = n->child; nn; nn = nn->next) { + PAIR_CLASS_INIT(&tag[0], "link-includes"); + i = 1; + if (h->base_includes) { + buffmt_includes(h, nn->string); + tag[i].key = ATTR_HREF; + tag[i++].val = h->buf; + } + t = print_otag(h, TAG_A, i, tag); + print_mdoc_node(m, nn, h); + print_tagq(h, t); + } + + h->flags |= HTML_NOSPACE; + print_text(h, ">"); + + return(0); +} + + +/* ARGSUSED */ +static int +mdoc_ic_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "cmd"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_rv_pre(MDOC_ARGS) +{ + const struct mdoc_node *nn; + struct htmlpair tag; + struct tag *t; + + print_otag(h, TAG_DIV, 0, NULL); + print_text(h, "The"); + + for (nn = n->child; nn; nn = nn->next) { + PAIR_CLASS_INIT(&tag, "fname"); + t = print_otag(h, TAG_SPAN, 1, &tag); + print_text(h, nn->string); + print_tagq(h, t); + + h->flags |= HTML_NOSPACE; + if (nn->next && NULL == nn->next->next) + print_text(h, "(), and"); + else if (nn->next) + print_text(h, "(),"); + else + print_text(h, "()"); + } + + if (n->child->next) + print_text(h, "functions return"); + else + print_text(h, "function returns"); + + print_text(h, "the value 0 if successful; otherwise the value " + "-1 is returned and the global variable"); + + PAIR_CLASS_INIT(&tag, "var"); + t = print_otag(h, TAG_SPAN, 1, &tag); + print_text(h, "errno"); + print_tagq(h, t); + print_text(h, "is set to indicate the error."); + return(0); +} + + +/* ARGSUSED */ +static int +mdoc_va_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "var"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_bq_pre(MDOC_ARGS) +{ + + if (MDOC_BODY != n->type) + return(1); + print_text(h, "\\(lB"); + h->flags |= HTML_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static void +mdoc_bq_post(MDOC_ARGS) +{ + + if (MDOC_BODY != n->type) + return; + h->flags |= HTML_NOSPACE; + print_text(h, "\\(rB"); +} + + +/* ARGSUSED */ +static int +mdoc_ap_pre(MDOC_ARGS) +{ + + h->flags |= HTML_NOSPACE; + print_text(h, "\\(aq"); + h->flags |= HTML_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_bf_pre(MDOC_ARGS) +{ + int i; + struct htmlpair tag[2]; + struct roffsu su; + + if (MDOC_HEAD == n->type) + return(0); + else if (MDOC_BLOCK != n->type) + return(1); + + PAIR_CLASS_INIT(&tag[0], "lit"); + + if (n->head->child) { + if ( ! strcmp("Em", n->head->child->string)) + PAIR_CLASS_INIT(&tag[0], "emph"); + else if ( ! strcmp("Sy", n->head->child->string)) + PAIR_CLASS_INIT(&tag[0], "symb"); + else if ( ! strcmp("Li", n->head->child->string)) + PAIR_CLASS_INIT(&tag[0], "lit"); + } else { + assert(n->args); + for (i = 0; i < (int)n->args->argc; i++) + switch (n->args->argv[i].arg) { + case (MDOC_Symbolic): + PAIR_CLASS_INIT(&tag[0], "symb"); + break; + case (MDOC_Literal): + PAIR_CLASS_INIT(&tag[0], "lit"); + break; + case (MDOC_Emphasis): + PAIR_CLASS_INIT(&tag[0], "emph"); + break; + default: + break; + } + } + + /* FIXME: div's have spaces stripped--we want them. */ + + bufcat_style(h, "display", "inline"); + SCALE_HS_INIT(&su, 1); + bufcat_su(h, "margin-right", &su); + PAIR_STYLE_INIT(&tag[1], h); + print_otag(h, TAG_DIV, 2, tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_ms_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "symb"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_pf_pre(MDOC_ARGS) +{ + + h->flags |= HTML_IGNDELIM; + return(1); +} + + +/* ARGSUSED */ +static void +mdoc_pf_post(MDOC_ARGS) +{ + + h->flags &= ~HTML_IGNDELIM; + h->flags |= HTML_NOSPACE; +} + + +/* ARGSUSED */ +static int +mdoc_rs_pre(MDOC_ARGS) +{ + struct htmlpair tag; + struct roffsu su; + + if (MDOC_BLOCK != n->type) + return(1); + + if (n->prev && SEC_SEE_ALSO == n->sec) { + SCALE_VS_INIT(&su, 1); + bufcat_su(h, "margin-top", &su); + PAIR_STYLE_INIT(&tag, h); + print_otag(h, TAG_DIV, 1, &tag); + } + + PAIR_CLASS_INIT(&tag, "ref"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + + +/* ARGSUSED */ +static int +mdoc_li_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "lit"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_sy_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "symb"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_bt_pre(MDOC_ARGS) +{ + + print_text(h, "is currently in beta test."); + return(0); +} + + +/* ARGSUSED */ +static int +mdoc_ud_pre(MDOC_ARGS) +{ + + print_text(h, "currently under development."); + return(0); +} + + +/* ARGSUSED */ +static int +mdoc_lb_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + if (SEC_SYNOPSIS == n->sec) + print_otag(h, TAG_DIV, 0, NULL); + PAIR_CLASS_INIT(&tag, "lib"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc__x_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + switch (n->tok) { + case(MDOC__A): + PAIR_CLASS_INIT(&tag, "ref-auth"); + break; + case(MDOC__B): + PAIR_CLASS_INIT(&tag, "ref-book"); + break; + case(MDOC__C): + PAIR_CLASS_INIT(&tag, "ref-city"); + break; + case(MDOC__D): + PAIR_CLASS_INIT(&tag, "ref-date"); + break; + case(MDOC__I): + PAIR_CLASS_INIT(&tag, "ref-issue"); + break; + case(MDOC__J): + PAIR_CLASS_INIT(&tag, "ref-jrnl"); + break; + case(MDOC__N): + PAIR_CLASS_INIT(&tag, "ref-num"); + break; + case(MDOC__O): + PAIR_CLASS_INIT(&tag, "ref-opt"); + break; + case(MDOC__P): + PAIR_CLASS_INIT(&tag, "ref-page"); + break; + case(MDOC__Q): + PAIR_CLASS_INIT(&tag, "ref-corp"); + break; + case(MDOC__R): + PAIR_CLASS_INIT(&tag, "ref-rep"); + break; + case(MDOC__T): + PAIR_CLASS_INIT(&tag, "ref-title"); + print_text(h, "\\(lq"); + h->flags |= HTML_NOSPACE; + break; + case(MDOC__V): + PAIR_CLASS_INIT(&tag, "ref-vol"); + break; + default: + abort(); + /* NOTREACHED */ + } + + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static void +mdoc__x_post(MDOC_ARGS) +{ + + h->flags |= HTML_NOSPACE; + switch (n->tok) { + case (MDOC__T): + print_text(h, "\\(rq"); + h->flags |= HTML_NOSPACE; + break; + default: + break; + } + print_text(h, n->next ? "," : "."); +} diff --git a/usr.bin/mandoc/mdoc_macro.c b/usr.bin/mandoc/mdoc_macro.c new file mode 100644 index 0000000000..26332ca1fb --- /dev/null +++ b/usr.bin/mandoc/mdoc_macro.c @@ -0,0 +1,1350 @@ +/* $Id: mdoc_macro.c,v 1.24 2009/09/21 21:11:37 schwarze Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include +#include +#include +#include + +#include "libmdoc.h" + +#define REWIND_REWIND (1 << 0) +#define REWIND_NOHALT (1 << 1) +#define REWIND_HALT (1 << 2) + +static int obsolete(MACRO_PROT_ARGS); +static int blk_part_exp(MACRO_PROT_ARGS); +static int in_line_eoln(MACRO_PROT_ARGS); +static int in_line_argn(MACRO_PROT_ARGS); +static int in_line(MACRO_PROT_ARGS); +static int blk_full(MACRO_PROT_ARGS); +static int blk_exp_close(MACRO_PROT_ARGS); +static int blk_part_imp(MACRO_PROT_ARGS); + +static int phrase(struct mdoc *, int, int, char *); +static int rew_dohalt(int, enum mdoc_type, + const struct mdoc_node *); +static int rew_alt(int); +static int rew_dobreak(int, const struct mdoc_node *); +static int rew_elem(struct mdoc *, int); +static int rew_sub(enum mdoc_type, struct mdoc *, + int, int, int); +static int rew_last(struct mdoc *, + const struct mdoc_node *); +static int append_delims(struct mdoc *, int, int *, char *); +static int lookup(int, const char *); +static int lookup_raw(const char *); +static int swarn(struct mdoc *, enum mdoc_type, int, int, + const struct mdoc_node *); + +/* Central table of library: who gets parsed how. */ + +const struct mdoc_macro __mdoc_macros[MDOC_MAX] = { + { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Ap */ + { in_line_eoln, MDOC_PROLOGUE }, /* Dd */ + { in_line_eoln, MDOC_PROLOGUE }, /* Dt */ + { in_line_eoln, MDOC_PROLOGUE }, /* Os */ + { blk_full, 0 }, /* Sh */ + { blk_full, 0 }, /* Ss */ + { in_line_eoln, 0 }, /* Pp */ + { blk_part_imp, MDOC_PARSED }, /* D1 */ + { blk_part_imp, MDOC_PARSED }, /* Dl */ + { blk_full, MDOC_EXPLICIT }, /* Bd */ + { blk_exp_close, MDOC_EXPLICIT }, /* Ed */ + { blk_full, MDOC_EXPLICIT }, /* Bl */ + { blk_exp_close, MDOC_EXPLICIT }, /* El */ + { blk_full, MDOC_PARSED }, /* It */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ad */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* An */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ar */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Cd */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Cm */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Dv */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Er */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ev */ + { in_line_eoln, 0 }, /* Ex */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Fa */ + { in_line_eoln, 0 }, /* Fd */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Fl */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Fn */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ft */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ic */ + { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* In */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Li */ + { blk_full, 0 }, /* Nd */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Nm */ + { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED }, /* Op */ + { obsolete, 0 }, /* Ot */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Pa */ + { in_line_eoln, 0 }, /* Rv */ + { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* St */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Va */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Vt */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Xr */ + { in_line_eoln, 0 }, /* %A */ + { in_line_eoln, 0 }, /* %B */ + { in_line_eoln, 0 }, /* %D */ + { in_line_eoln, 0 }, /* %I */ + { in_line_eoln, 0 }, /* %J */ + { in_line_eoln, 0 }, /* %N */ + { in_line_eoln, 0 }, /* %O */ + { in_line_eoln, 0 }, /* %P */ + { in_line_eoln, 0 }, /* %R */ + { in_line_eoln, 0 }, /* %T */ + { in_line_eoln, 0 }, /* %V */ + { blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Ac */ + { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Ao */ + { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED }, /* Aq */ + { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* At */ + { blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Bc */ + { blk_full, MDOC_EXPLICIT }, /* Bf */ + { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Bo */ + { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED }, /* Bq */ + { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Bsx */ + { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Bx */ + { in_line_eoln, 0 }, /* Db */ + { blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Dc */ + { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Do */ + { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED }, /* Dq */ + { blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Ec */ + { blk_exp_close, MDOC_EXPLICIT }, /* Ef */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Em */ + { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Eo */ + { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Fx */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ms */ + { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* No */ + { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Ns */ + { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Nx */ + { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Ox */ + { blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Pc */ + { in_line_argn, MDOC_PARSED | MDOC_IGNDELIM }, /* Pf */ + { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Po */ + { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED }, /* Pq */ + { blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Qc */ + { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED }, /* Ql */ + { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Qo */ + { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED }, /* Qq */ + { blk_exp_close, MDOC_EXPLICIT }, /* Re */ + { blk_full, MDOC_EXPLICIT }, /* Rs */ + { blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Sc */ + { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* So */ + { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED }, /* Sq */ + { in_line_eoln, 0 }, /* Sm */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Sx */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Sy */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Tn */ + { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Ux */ + { blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Xc */ + { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Xo */ + { blk_full, MDOC_EXPLICIT | MDOC_CALLABLE }, /* Fo */ + { blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Fc */ + { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Oo */ + { blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Oc */ + { blk_full, MDOC_EXPLICIT }, /* Bk */ + { blk_exp_close, MDOC_EXPLICIT }, /* Ek */ + { in_line_eoln, 0 }, /* Bt */ + { in_line_eoln, 0 }, /* Hf */ + { obsolete, 0 }, /* Fr */ + { in_line_eoln, 0 }, /* Ud */ + { in_line_eoln, 0 }, /* Lb */ + { in_line_eoln, 0 }, /* Lp */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Lk */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Mt */ + { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED }, /* Brq */ + { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Bro */ + { blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Brc */ + { in_line_eoln, 0 }, /* %C */ + { obsolete, 0 }, /* Es */ + { obsolete, 0 }, /* En */ + { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Dx */ + { in_line_eoln, 0 }, /* %Q */ + { in_line_eoln, 0 }, /* br */ + { in_line_eoln, 0 }, /* sp */ +}; + +const struct mdoc_macro * const mdoc_macros = __mdoc_macros; + + +static int +swarn(struct mdoc *mdoc, enum mdoc_type type, + int line, int pos, const struct mdoc_node *p) +{ + const char *n, *t, *tt; + + n = t = ""; + tt = "block"; + + switch (type) { + case (MDOC_BODY): + tt = "multi-line"; + break; + case (MDOC_HEAD): + tt = "line"; + break; + default: + break; + } + + switch (p->type) { + case (MDOC_BLOCK): + n = mdoc_macronames[p->tok]; + t = "block"; + break; + case (MDOC_BODY): + n = mdoc_macronames[p->tok]; + t = "multi-line"; + break; + case (MDOC_HEAD): + n = mdoc_macronames[p->tok]; + t = "line"; + break; + default: + break; + } + + if ( ! (MDOC_IGN_SCOPE & mdoc->pflags)) + return(mdoc_verr(mdoc, line, pos, + "%s scope breaks %s scope of %s", + tt, t, n)); + return(mdoc_vwarn(mdoc, line, pos, + "%s scope breaks %s scope of %s", + tt, t, n)); +} + + +/* + * This is called at the end of parsing. It must traverse up the tree, + * closing out open [implicit] scopes. Obviously, open explicit scopes + * are errors. + */ +int +mdoc_macroend(struct mdoc *m) +{ + struct mdoc_node *n; + + /* Scan for open explicit scopes. */ + + n = MDOC_VALID & m->last->flags ? m->last->parent : m->last; + + for ( ; n; n = n->parent) { + if (MDOC_BLOCK != n->type) + continue; + if ( ! (MDOC_EXPLICIT & mdoc_macros[n->tok].flags)) + continue; + return(mdoc_nerr(m, n, EOPEN)); + } + + /* Rewind to the first. */ + + return(rew_last(m, m->first)); +} + + +/* + * Look up a macro from within a subsequent context. + */ +static int +lookup(int from, const char *p) +{ + /* FIXME: make -diag lists be un-PARSED. */ + + if ( ! (MDOC_PARSED & mdoc_macros[from].flags)) + return(MDOC_MAX); + return(lookup_raw(p)); +} + + +/* + * Lookup a macro following the initial line macro. + */ +static int +lookup_raw(const char *p) +{ + int res; + + if (MDOC_MAX == (res = mdoc_hash_find(p))) + return(MDOC_MAX); + if (MDOC_CALLABLE & mdoc_macros[res].flags) + return(res); + return(MDOC_MAX); +} + + +static int +rew_last(struct mdoc *mdoc, const struct mdoc_node *to) +{ + + assert(to); + mdoc->next = MDOC_NEXT_SIBLING; + + /* LINTED */ + while (mdoc->last != to) { + if ( ! mdoc_valid_post(mdoc)) + return(0); + if ( ! mdoc_action_post(mdoc)) + return(0); + mdoc->last = mdoc->last->parent; + assert(mdoc->last); + } + + if ( ! mdoc_valid_post(mdoc)) + return(0); + return(mdoc_action_post(mdoc)); +} + + +/* + * Return the opening macro of a closing one, e.g., `Ec' has `Eo' as its + * matching pair. + */ +static int +rew_alt(int tok) +{ + switch (tok) { + case (MDOC_Ac): + return(MDOC_Ao); + case (MDOC_Bc): + return(MDOC_Bo); + case (MDOC_Brc): + return(MDOC_Bro); + case (MDOC_Dc): + return(MDOC_Do); + case (MDOC_Ec): + return(MDOC_Eo); + case (MDOC_Ed): + return(MDOC_Bd); + case (MDOC_Ef): + return(MDOC_Bf); + case (MDOC_Ek): + return(MDOC_Bk); + case (MDOC_El): + return(MDOC_Bl); + case (MDOC_Fc): + return(MDOC_Fo); + case (MDOC_Oc): + return(MDOC_Oo); + case (MDOC_Pc): + return(MDOC_Po); + case (MDOC_Qc): + return(MDOC_Qo); + case (MDOC_Re): + return(MDOC_Rs); + case (MDOC_Sc): + return(MDOC_So); + case (MDOC_Xc): + return(MDOC_Xo); + default: + break; + } + abort(); + /* NOTREACHED */ +} + + +/* + * Rewind rules. This indicates whether to stop rewinding + * (REWIND_HALT) without touching our current scope, stop rewinding and + * close our current scope (REWIND_REWIND), or continue (REWIND_NOHALT). + * The scope-closing and so on occurs in the various rew_* routines. + */ +static int +rew_dohalt(int tok, enum mdoc_type type, const struct mdoc_node *p) +{ + + if (MDOC_ROOT == p->type) + return(REWIND_HALT); + if (MDOC_VALID & p->flags) + return(REWIND_NOHALT); + + switch (tok) { + case (MDOC_Aq): + /* FALLTHROUGH */ + case (MDOC_Bq): + /* FALLTHROUGH */ + case (MDOC_Brq): + /* FALLTHROUGH */ + case (MDOC_D1): + /* FALLTHROUGH */ + case (MDOC_Dl): + /* FALLTHROUGH */ + case (MDOC_Dq): + /* FALLTHROUGH */ + case (MDOC_Op): + /* FALLTHROUGH */ + case (MDOC_Pq): + /* FALLTHROUGH */ + case (MDOC_Ql): + /* FALLTHROUGH */ + case (MDOC_Qq): + /* FALLTHROUGH */ + case (MDOC_Sq): + assert(MDOC_TAIL != type); + if (type == p->type && tok == p->tok) + return(REWIND_REWIND); + break; + case (MDOC_It): + assert(MDOC_TAIL != type); + if (type == p->type && tok == p->tok) + return(REWIND_REWIND); + if (MDOC_BODY == p->type && MDOC_Bl == p->tok) + return(REWIND_HALT); + break; + case (MDOC_Sh): + if (type == p->type && tok == p->tok) + return(REWIND_REWIND); + break; + case (MDOC_Nd): + /* FALLTHROUGH */ + case (MDOC_Ss): + assert(MDOC_TAIL != type); + if (type == p->type && tok == p->tok) + return(REWIND_REWIND); + if (MDOC_BODY == p->type && MDOC_Sh == p->tok) + return(REWIND_HALT); + break; + case (MDOC_Ao): + /* FALLTHROUGH */ + case (MDOC_Bd): + /* FALLTHROUGH */ + case (MDOC_Bf): + /* FALLTHROUGH */ + case (MDOC_Bk): + /* FALLTHROUGH */ + case (MDOC_Bl): + /* FALLTHROUGH */ + case (MDOC_Bo): + /* FALLTHROUGH */ + case (MDOC_Bro): + /* FALLTHROUGH */ + case (MDOC_Do): + /* FALLTHROUGH */ + case (MDOC_Eo): + /* FALLTHROUGH */ + case (MDOC_Fo): + /* FALLTHROUGH */ + case (MDOC_Oo): + /* FALLTHROUGH */ + case (MDOC_Po): + /* FALLTHROUGH */ + case (MDOC_Qo): + /* FALLTHROUGH */ + case (MDOC_Rs): + /* FALLTHROUGH */ + case (MDOC_So): + /* FALLTHROUGH */ + case (MDOC_Xo): + if (type == p->type && tok == p->tok) + return(REWIND_REWIND); + break; + /* Multi-line explicit scope close. */ + case (MDOC_Ac): + /* FALLTHROUGH */ + case (MDOC_Bc): + /* FALLTHROUGH */ + case (MDOC_Brc): + /* FALLTHROUGH */ + case (MDOC_Dc): + /* FALLTHROUGH */ + case (MDOC_Ec): + /* FALLTHROUGH */ + case (MDOC_Ed): + /* FALLTHROUGH */ + case (MDOC_Ek): + /* FALLTHROUGH */ + case (MDOC_El): + /* FALLTHROUGH */ + case (MDOC_Fc): + /* FALLTHROUGH */ + case (MDOC_Ef): + /* FALLTHROUGH */ + case (MDOC_Oc): + /* FALLTHROUGH */ + case (MDOC_Pc): + /* FALLTHROUGH */ + case (MDOC_Qc): + /* FALLTHROUGH */ + case (MDOC_Re): + /* FALLTHROUGH */ + case (MDOC_Sc): + /* FALLTHROUGH */ + case (MDOC_Xc): + if (type == p->type && rew_alt(tok) == p->tok) + return(REWIND_REWIND); + break; + default: + abort(); + /* NOTREACHED */ + } + + return(REWIND_NOHALT); +} + + +/* + * See if we can break an encountered scope (the rew_dohalt has returned + * REWIND_NOHALT). + */ +static int +rew_dobreak(int tok, const struct mdoc_node *p) +{ + + assert(MDOC_ROOT != p->type); + if (MDOC_ELEM == p->type) + return(1); + if (MDOC_TEXT == p->type) + return(1); + if (MDOC_VALID & p->flags) + return(1); + + switch (tok) { + case (MDOC_It): + return(MDOC_It == p->tok); + case (MDOC_Nd): + return(MDOC_Nd == p->tok); + case (MDOC_Ss): + return(MDOC_Ss == p->tok); + case (MDOC_Sh): + if (MDOC_Nd == p->tok) + return(1); + if (MDOC_Ss == p->tok) + return(1); + return(MDOC_Sh == p->tok); + case (MDOC_El): + if (MDOC_It == p->tok) + return(1); + break; + case (MDOC_Oc): + /* XXX - experimental! */ + if (MDOC_Op == p->tok) + return(1); + break; + default: + break; + } + + if (MDOC_EXPLICIT & mdoc_macros[tok].flags) + return(p->tok == rew_alt(tok)); + else if (MDOC_BLOCK == p->type) + return(1); + + return(tok == p->tok); +} + + +static int +rew_elem(struct mdoc *mdoc, int tok) +{ + struct mdoc_node *n; + + n = mdoc->last; + if (MDOC_ELEM != n->type) + n = n->parent; + assert(MDOC_ELEM == n->type); + assert(tok == n->tok); + + return(rew_last(mdoc, n)); +} + + +static int +rew_sub(enum mdoc_type t, struct mdoc *m, + int tok, int line, int ppos) +{ + struct mdoc_node *n; + int c; + + /* LINTED */ + for (n = m->last; n; n = n->parent) { + c = rew_dohalt(tok, t, n); + if (REWIND_HALT == c) { + if (MDOC_BLOCK != t) + return(1); + if ( ! (MDOC_EXPLICIT & mdoc_macros[tok].flags)) + return(1); + return(mdoc_perr(m, line, ppos, ENOCTX)); + } + if (REWIND_REWIND == c) + break; + else if (rew_dobreak(tok, n)) + continue; + if ( ! swarn(m, t, line, ppos, n)) + return(0); + } + + assert(n); + return(rew_last(m, n)); +} + + +static int +append_delims(struct mdoc *mdoc, int line, int *pos, char *buf) +{ + int c, lastarg; + char *p; + + if (0 == buf[*pos]) + return(1); + + for (;;) { + lastarg = *pos; + c = mdoc_zargs(mdoc, line, pos, buf, ARGS_NOWARN, &p); + assert(ARGS_PHRASE != c); + + if (ARGS_ERROR == c) + return(0); + else if (ARGS_EOLN == c) + break; + assert(mdoc_isdelim(p)); + if ( ! mdoc_word_alloc(mdoc, line, lastarg, p)) + return(0); + } + + return(1); +} + + +/* + * Close out block partial/full explicit. + */ +static int +blk_exp_close(MACRO_PROT_ARGS) +{ + int j, c, lastarg, maxargs, flushed; + char *p; + + switch (tok) { + case (MDOC_Ec): + maxargs = 1; + break; + default: + maxargs = 0; + break; + } + + if ( ! (MDOC_CALLABLE & mdoc_macros[tok].flags)) { + if (buf[*pos]) + if ( ! mdoc_pwarn(m, line, ppos, ENOLINE)) + return(0); + + if ( ! rew_sub(MDOC_BODY, m, tok, line, ppos)) + return(0); + return(rew_sub(MDOC_BLOCK, m, tok, line, ppos)); + } + + if ( ! rew_sub(MDOC_BODY, m, tok, line, ppos)) + return(0); + + if (maxargs > 0) + if ( ! mdoc_tail_alloc(m, line, ppos, rew_alt(tok))) + return(0); + + for (flushed = j = 0; ; j++) { + lastarg = *pos; + + if (j == maxargs && ! flushed) { + if ( ! rew_sub(MDOC_BLOCK, m, tok, line, ppos)) + return(0); + flushed = 1; + } + + c = mdoc_args(m, line, pos, buf, tok, &p); + + if (ARGS_ERROR == c) + return(0); + if (ARGS_PUNCT == c) + break; + if (ARGS_EOLN == c) + break; + + if (MDOC_MAX != (c = lookup(tok, p))) { + if ( ! flushed) { + if ( ! rew_sub(MDOC_BLOCK, m, tok, line, ppos)) + return(0); + flushed = 1; + } + if ( ! mdoc_macro(m, c, line, lastarg, pos, buf)) + return(0); + break; + } + + if ( ! mdoc_word_alloc(m, line, lastarg, p)) + return(0); + } + + if ( ! flushed && ! rew_sub(MDOC_BLOCK, m, tok, line, ppos)) + return(0); + + if (ppos > 1) + return(1); + return(append_delims(m, line, pos, buf)); +} + + +static int +in_line(MACRO_PROT_ARGS) +{ + int la, lastpunct, c, w, cnt, d, nc; + struct mdoc_arg *arg; + char *p; + + /* + * Whether we allow ignored elements (those without content, + * usually because of reserved words) to squeak by. + */ + switch (tok) { + case (MDOC_An): + /* FALLTHROUGH */ + case (MDOC_Ar): + /* FALLTHROUGH */ + case (MDOC_Fl): + /* FALLTHROUGH */ + case (MDOC_Lk): + /* FALLTHROUGH */ + case (MDOC_Nm): + /* FALLTHROUGH */ + case (MDOC_Pa): + nc = 1; + break; + default: + nc = 0; + break; + } + + for (arg = NULL;; ) { + la = *pos; + c = mdoc_argv(m, line, tok, &arg, pos, buf); + + if (ARGV_WORD == c) { + *pos = la; + break; + } + if (ARGV_EOLN == c) + break; + if (ARGV_ARG == c) + continue; + + mdoc_argv_free(arg); + return(0); + } + + for (cnt = 0, lastpunct = 1;; ) { + la = *pos; + w = mdoc_args(m, line, pos, buf, tok, &p); + + if (ARGS_ERROR == w) + return(0); + if (ARGS_EOLN == w) + break; + if (ARGS_PUNCT == w) + break; + + /* Quoted words shouldn't be looked-up. */ + + c = ARGS_QWORD == w ? MDOC_MAX : lookup(tok, p); + + /* + * In this case, we've located a submacro and must + * execute it. Close out scope, if open. If no + * elements have been generated, either create one (nc) + * or raise a warning. + */ + + if (MDOC_MAX != c) { + if (0 == lastpunct && ! rew_elem(m, tok)) + return(0); + if (nc && 0 == cnt) { + if ( ! mdoc_elem_alloc(m, line, ppos, tok, arg)) + return(0); + if ( ! rew_last(m, m->last)) + return(0); + } else if ( ! nc && 0 == cnt) { + mdoc_argv_free(arg); + if ( ! mdoc_pwarn(m, line, ppos, EIGNE)) + return(0); + } + c = mdoc_macro(m, c, line, la, pos, buf); + if (0 == c) + return(0); + if (ppos > 1) + return(1); + return(append_delims(m, line, pos, buf)); + } + + /* + * Non-quote-enclosed punctuation. Set up our scope, if + * a word; rewind the scope, if a delimiter; then append + * the word. + */ + + d = mdoc_isdelim(p); + + if (ARGS_QWORD != w && d) { + if (0 == lastpunct && ! rew_elem(m, tok)) + return(0); + lastpunct = 1; + } else if (lastpunct) { + if ( ! mdoc_elem_alloc(m, line, ppos, tok, arg)) + return(0); + lastpunct = 0; + } + + if ( ! d) + cnt++; + if ( ! mdoc_word_alloc(m, line, la, p)) + return(0); + } + + if (0 == lastpunct && ! rew_elem(m, tok)) + return(0); + + /* + * If no elements have been collected and we're allowed to have + * empties (nc), open a scope and close it out. Otherwise, + * raise a warning. + * + */ + if (nc && 0 == cnt) { + if ( ! mdoc_elem_alloc(m, line, ppos, tok, arg)) + return(0); + if ( ! rew_last(m, m->last)) + return(0); + } else if ( ! nc && 0 == cnt) { + mdoc_argv_free(arg); + if ( ! mdoc_pwarn(m, line, ppos, EIGNE)) + return(0); + } + + if (ppos > 1) + return(1); + return(append_delims(m, line, pos, buf)); +} + + +static int +blk_full(MACRO_PROT_ARGS) +{ + int c, lastarg, reopen, dohead; + struct mdoc_arg *arg; + char *p; + + /* + * Whether to process a block-head section. If this is + * non-zero, then a head will be opened for all line arguments. + * If not, then the head will always be empty and only a body + * will be opened, which will stay open at the eoln. + */ + + switch (tok) { + case (MDOC_Nd): + dohead = 0; + break; + default: + dohead = 1; + break; + } + + if ( ! (MDOC_EXPLICIT & mdoc_macros[tok].flags)) { + if ( ! rew_sub(MDOC_BODY, m, tok, line, ppos)) + return(0); + if ( ! rew_sub(MDOC_BLOCK, m, tok, line, ppos)) + return(0); + } + + for (arg = NULL;; ) { + lastarg = *pos; + c = mdoc_argv(m, line, tok, &arg, pos, buf); + + if (ARGV_WORD == c) { + *pos = lastarg; + break; + } + + if (ARGV_EOLN == c) + break; + if (ARGV_ARG == c) + continue; + + mdoc_argv_free(arg); + return(0); + } + + if ( ! mdoc_block_alloc(m, line, ppos, tok, arg)) + return(0); + + if (0 == buf[*pos]) { + if ( ! mdoc_head_alloc(m, line, ppos, tok)) + return(0); + if ( ! rew_sub(MDOC_HEAD, m, tok, line, ppos)) + return(0); + if ( ! mdoc_body_alloc(m, line, ppos, tok)) + return(0); + return(1); + } + + if ( ! mdoc_head_alloc(m, line, ppos, tok)) + return(0); + + /* Immediately close out head and enter body, if applicable. */ + + if (0 == dohead) { + if ( ! rew_sub(MDOC_HEAD, m, tok, line, ppos)) + return(0); + if ( ! mdoc_body_alloc(m, line, ppos, tok)) + return(0); + } + + for (reopen = 0;; ) { + lastarg = *pos; + c = mdoc_args(m, line, pos, buf, tok, &p); + + if (ARGS_ERROR == c) + return(0); + if (ARGS_EOLN == c) + break; + if (ARGS_PHRASE == c) { + assert(dohead); + if (reopen && ! mdoc_head_alloc(m, line, ppos, tok)) + return(0); + /* + * Phrases are self-contained macro phrases used + * in the columnar output of a macro. They need + * special handling. + */ + if ( ! phrase(m, line, lastarg, buf)) + return(0); + if ( ! rew_sub(MDOC_HEAD, m, tok, line, ppos)) + return(0); + + reopen = 1; + continue; + } + + if (MDOC_MAX == (c = lookup(tok, p))) { + if ( ! mdoc_word_alloc(m, line, lastarg, p)) + return(0); + continue; + } + + if ( ! mdoc_macro(m, c, line, lastarg, pos, buf)) + return(0); + break; + } + + if (1 == ppos && ! append_delims(m, line, pos, buf)) + return(0); + + /* If the body's already open, then just return. */ + if (0 == dohead) + return(1); + + if ( ! rew_sub(MDOC_HEAD, m, tok, line, ppos)) + return(0); + if ( ! mdoc_body_alloc(m, line, ppos, tok)) + return(0); + + return(1); +} + + +static int +blk_part_imp(MACRO_PROT_ARGS) +{ + int la, c; + char *p; + struct mdoc_node *blk, *body, *n; + + /* If applicable, close out prior scopes. */ + + if ( ! mdoc_block_alloc(m, line, ppos, tok, NULL)) + return(0); + /* Saved for later close-out. */ + blk = m->last; + if ( ! mdoc_head_alloc(m, line, ppos, tok)) + return(0); + if ( ! rew_sub(MDOC_HEAD, m, tok, line, ppos)) + return(0); + if ( ! mdoc_body_alloc(m, line, ppos, tok)) + return(0); + /* Saved for later close-out. */ + body = m->last; + + /* Body argument processing. */ + + for (;;) { + la = *pos; + c = mdoc_args(m, line, pos, buf, tok, &p); + assert(ARGS_PHRASE != c); + + if (ARGS_ERROR == c) + return(0); + if (ARGS_PUNCT == c) + break; + if (ARGS_EOLN == c) + break; + + if (MDOC_MAX == (c = lookup(tok, p))) { + if ( ! mdoc_word_alloc(m, line, la, p)) + return(0); + continue; + } + + if ( ! mdoc_macro(m, c, line, la, pos, buf)) + return(0); + break; + } + + /* + * If we can't rewind to our body, then our scope has already + * been closed by another macro (like `Oc' closing `Op'). This + * is ugly behaviour nodding its head to OpenBSD's overwhelming + * crufty use of `Op' breakage--XXX, deprecate in time. + */ + for (n = m->last; n; n = n->parent) + if (body == n) + break; + if (NULL == n && ! mdoc_nwarn(m, body, EIMPBRK)) + return(0); + if (n && ! rew_last(m, body)) + return(0); + + /* Standard appending of delimiters. */ + + if (1 == ppos && ! append_delims(m, line, pos, buf)) + return(0); + + /* Rewind scope, if applicable. */ + + if (n && ! rew_last(m, blk)) + return(0); + + return(1); +} + + +static int +blk_part_exp(MACRO_PROT_ARGS) +{ + int la, flushed, j, c, maxargs; + char *p; + + /* Number of head arguments. Only `Eo' has these, */ + + switch (tok) { + case (MDOC_Eo): + maxargs = 1; + break; + default: + maxargs = 0; + break; + } + + /* Begin the block scope. */ + + if ( ! mdoc_block_alloc(m, line, ppos, tok, NULL)) + return(0); + + /* + * If no head arguments, open and then close out a head, noting + * that we've flushed our terms. `flushed' means that we've + * flushed out the head and the body is open. + */ + + if (0 == maxargs) { + if ( ! mdoc_head_alloc(m, line, ppos, tok)) + return(0); + if ( ! rew_sub(MDOC_HEAD, m, tok, line, ppos)) + return(0); + if ( ! mdoc_body_alloc(m, line, ppos, tok)) + return(0); + flushed = 1; + } else { + if ( ! mdoc_head_alloc(m, line, ppos, tok)) + return(0); + flushed = 0; + } + + /* Process the head/head+body line arguments. */ + + for (j = 0; ; j++) { + la = *pos; + if (j == maxargs && ! flushed) { + if ( ! rew_sub(MDOC_HEAD, m, tok, line, ppos)) + return(0); + flushed = 1; + if ( ! mdoc_body_alloc(m, line, ppos, tok)) + return(0); + } + + c = mdoc_args(m, line, pos, buf, tok, &p); + assert(ARGS_PHRASE != c); + + if (ARGS_ERROR == c) + return(0); + if (ARGS_PUNCT == c) + break; + if (ARGS_EOLN == c) + break; + + if (MDOC_MAX != (c = lookup(tok, p))) { + if ( ! flushed) { + if ( ! rew_sub(MDOC_HEAD, m, tok, line, ppos)) + return(0); + flushed = 1; + if ( ! mdoc_body_alloc(m, line, ppos, tok)) + return(0); + } + if ( ! mdoc_macro(m, c, line, la, pos, buf)) + return(0); + break; + } + + if ( ! flushed && mdoc_isdelim(p)) { + if ( ! rew_sub(MDOC_HEAD, m, tok, line, ppos)) + return(0); + flushed = 1; + if ( ! mdoc_body_alloc(m, line, ppos, tok)) + return(0); + } + + if ( ! mdoc_word_alloc(m, line, la, p)) + return(0); + } + + /* Close the head and open the body, if applicable. */ + + if ( ! flushed) { + if ( ! rew_sub(MDOC_HEAD, m, tok, line, ppos)) + return(0); + if ( ! mdoc_body_alloc(m, line, ppos, tok)) + return(0); + } + + /* Standard appending of delimiters. */ + + if (ppos > 1) + return(1); + return(append_delims(m, line, pos, buf)); +} + + +static int +in_line_argn(MACRO_PROT_ARGS) +{ + int la, flushed, j, c, maxargs; + struct mdoc_arg *arg; + char *p; + + /* Fixed maximum arguments per macro, if applicable. */ + + switch (tok) { + case (MDOC_Ap): + /* FALLTHROUGH */ + case (MDOC_No): + /* FALLTHROUGH */ + case (MDOC_Ns): + /* FALLTHROUGH */ + case (MDOC_Ux): + maxargs = 0; + break; + default: + maxargs = 1; + break; + } + + /* Macro argument processing. */ + + for (arg = NULL;; ) { + la = *pos; + c = mdoc_argv(m, line, tok, &arg, pos, buf); + + if (ARGV_WORD == c) { + *pos = la; + break; + } + + if (ARGV_EOLN == c) + break; + if (ARGV_ARG == c) + continue; + + mdoc_argv_free(arg); + return(0); + } + + /* Open the element scope. */ + + if ( ! mdoc_elem_alloc(m, line, ppos, tok, arg)) + return(0); + + /* Process element arguments. */ + + for (flushed = j = 0; ; j++) { + la = *pos; + + if (j == maxargs && ! flushed) { + if ( ! rew_elem(m, tok)) + return(0); + flushed = 1; + } + + c = mdoc_args(m, line, pos, buf, tok, &p); + + if (ARGS_ERROR == c) + return(0); + if (ARGS_PUNCT == c) + break; + if (ARGS_EOLN == c) + break; + + if (MDOC_MAX != (c = lookup(tok, p))) { + if ( ! flushed && ! rew_elem(m, tok)) + return(0); + flushed = 1; + if ( ! mdoc_macro(m, c, line, la, pos, buf)) + return(0); + break; + } + + if ( ! (MDOC_IGNDELIM & mdoc_macros[tok].flags) && + ! flushed && mdoc_isdelim(p)) { + if ( ! rew_elem(m, tok)) + return(0); + flushed = 1; + } + + if ( ! mdoc_word_alloc(m, line, la, p)) + return(0); + } + + /* Close out and append delimiters. */ + + if ( ! flushed && ! rew_elem(m, tok)) + return(0); + + if (ppos > 1) + return(1); + return(append_delims(m, line, pos, buf)); +} + + +static int +in_line_eoln(MACRO_PROT_ARGS) +{ + int c, w, la; + struct mdoc_arg *arg; + char *p; + + assert( ! (MDOC_PARSED & mdoc_macros[tok].flags)); + + /* Parse macro arguments. */ + + for (arg = NULL; ; ) { + la = *pos; + c = mdoc_argv(m, line, tok, &arg, pos, buf); + + if (ARGV_WORD == c) { + *pos = la; + break; + } + if (ARGV_EOLN == c) + break; + if (ARGV_ARG == c) + continue; + + mdoc_argv_free(arg); + return(0); + } + + /* Open element scope. */ + + if ( ! mdoc_elem_alloc(m, line, ppos, tok, arg)) + return(0); + + /* Parse argument terms. */ + + for (;;) { + la = *pos; + w = mdoc_args(m, line, pos, buf, tok, &p); + + if (ARGS_ERROR == w) + return(0); + if (ARGS_EOLN == w) + break; + + c = ARGS_QWORD == w ? MDOC_MAX : lookup(tok, p); + + if (MDOC_MAX != c) { + if ( ! rew_elem(m, tok)) + return(0); + return(mdoc_macro(m, c, line, la, pos, buf)); + } + + if ( ! mdoc_word_alloc(m, line, la, p)) + return(0); + } + + /* Close out (no delimiters). */ + + return(rew_elem(m, tok)); +} + + +/* ARGSUSED */ +static int +obsolete(MACRO_PROT_ARGS) +{ + + return(mdoc_pwarn(m, line, ppos, EOBS)); +} + + +/* + * Phrases occur within `Bl -column' entries, separated by `Ta' or tabs. + * They're unusual because they're basically free-form text until a + * macro is encountered. + */ +static int +phrase(struct mdoc *m, int line, int ppos, char *buf) +{ + int c, w, la, pos; + char *p; + + for (pos = ppos; ; ) { + la = pos; + + /* Note: no calling context! */ + w = mdoc_zargs(m, line, &pos, buf, 0, &p); + + if (ARGS_ERROR == w) + return(0); + if (ARGS_EOLN == w) + break; + + c = ARGS_QWORD == w ? MDOC_MAX : lookup_raw(p); + + if (MDOC_MAX != c) { + if ( ! mdoc_macro(m, c, line, la, &pos, buf)) + return(0); + return(append_delims(m, line, &pos, buf)); + } + + if ( ! mdoc_word_alloc(m, line, la, p)) + return(0); + } + + return(1); +} diff --git a/usr.bin/mandoc/mdoc_strings.c b/usr.bin/mandoc/mdoc_strings.c new file mode 100644 index 0000000000..05502c4905 --- /dev/null +++ b/usr.bin/mandoc/mdoc_strings.c @@ -0,0 +1,237 @@ +/* $Id: mdoc_strings.c,v 1.9 2009/08/22 22:11:24 schwarze Exp $ */ +/* + * Copyright (c) 2008 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include + +#include +#include +#include +#include + +#include "libmdoc.h" + +/* FIXME: this file is poorly named. */ + +struct mdoc_secname { + const char *name; /* Name of section. */ + enum mdoc_sec sec; /* Corresponding section. */ +}; + +#define SECNAME_MAX (20) + +static const struct mdoc_secname secnames[SECNAME_MAX] = { + { "NAME", SEC_NAME }, + { "LIBRARY", SEC_LIBRARY }, + { "SYNOPSIS", SEC_SYNOPSIS }, + { "DESCRIPTION", SEC_DESCRIPTION }, + { "IMPLEMENTATION NOTES", SEC_IMPLEMENTATION }, + { "EXIT STATUS", SEC_EXIT_STATUS }, + { "RETURN VALUES", SEC_RETURN_VALUES }, + { "ENVIRONMENT", SEC_ENVIRONMENT }, + { "FILES", SEC_FILES }, + { "EXAMPLES", SEC_EXAMPLES }, + { "DIAGNOSTICS", SEC_DIAGNOSTICS }, + { "COMPATIBILITY", SEC_COMPATIBILITY }, + { "ERRORS", SEC_ERRORS }, + { "SEE ALSO", SEC_SEE_ALSO }, + { "STANDARDS", SEC_STANDARDS }, + { "HISTORY", SEC_HISTORY }, + { "AUTHORS", SEC_AUTHORS }, + { "CAVEATS", SEC_CAVEATS }, + { "BUGS", SEC_BUGS }, + { "SECURITY CONSIDERATIONS", SEC_SECURITY } +}; + + +int +mdoc_iscdelim(char p) +{ + + switch (p) { + case('|'): + /* FALLTHROUGH */ + case('.'): + /* FALLTHROUGH */ + case(','): + /* FALLTHROUGH */ + case(';'): + /* FALLTHROUGH */ + case(':'): + /* FALLTHROUGH */ + case('?'): + /* FALLTHROUGH */ + case('!'): + /* FALLTHROUGH */ + case('('): + /* FALLTHROUGH */ + case(')'): + /* FALLTHROUGH */ + case('['): + /* FALLTHROUGH */ + case(']'): + /* FALLTHROUGH */ + case('{'): + /* FALLTHROUGH */ + case('}'): + return(1); + default: + break; + } + + return(0); +} + + +int +mdoc_isdelim(const char *p) +{ + + if (0 == *p) + return(0); + if (0 != *(p + 1)) + return(0); + return(mdoc_iscdelim(*p)); +} + + +enum mdoc_sec +mdoc_atosec(const char *p) +{ + int i; + + for (i = 0; i < SECNAME_MAX; i++) + if (0 == strcmp(p, secnames[i].name)) + return(secnames[i].sec); + + return(SEC_CUSTOM); +} + + +time_t +mdoc_atotime(const char *p) +{ + struct tm tm; + char *pp; + + bzero(&tm, sizeof(struct tm)); + + if (0 == strcmp(p, "$" "Mdocdate$")) + return(time(NULL)); + if ((pp = strptime(p, "$" "Mdocdate: %b %d %Y $", &tm)) && 0 == *pp) + return(mktime(&tm)); + /* XXX - this matches "June 1999", which is wrong. */ + if ((pp = strptime(p, "%b %d %Y", &tm)) && 0 == *pp) + return(mktime(&tm)); + if ((pp = strptime(p, "%b %d, %Y", &tm)) && 0 == *pp) + return(mktime(&tm)); + + return(0); +} + + +/* FIXME: move this into an editable .in file. */ +size_t +mdoc_macro2len(int macro) +{ + + switch (macro) { + case(MDOC_Ad): + return(12); + case(MDOC_Ao): + return(12); + case(MDOC_An): + return(12); + case(MDOC_Aq): + return(12); + case(MDOC_Ar): + return(12); + case(MDOC_Bo): + return(12); + case(MDOC_Bq): + return(12); + case(MDOC_Cd): + return(12); + case(MDOC_Cm): + return(10); + case(MDOC_Do): + return(10); + case(MDOC_Dq): + return(12); + case(MDOC_Dv): + return(12); + case(MDOC_Eo): + return(12); + case(MDOC_Em): + return(10); + case(MDOC_Er): + return(12); + case(MDOC_Ev): + return(15); + case(MDOC_Fa): + return(12); + case(MDOC_Fl): + return(10); + case(MDOC_Fo): + return(16); + case(MDOC_Fn): + return(16); + case(MDOC_Ic): + return(10); + case(MDOC_Li): + return(16); + case(MDOC_Ms): + return(6); + case(MDOC_Nm): + return(10); + case(MDOC_No): + return(12); + case(MDOC_Oo): + return(10); + case(MDOC_Op): + return(14); + case(MDOC_Pa): + return(32); + case(MDOC_Pf): + return(12); + case(MDOC_Po): + return(12); + case(MDOC_Pq): + return(12); + case(MDOC_Ql): + return(16); + case(MDOC_Qo): + return(12); + case(MDOC_So): + return(12); + case(MDOC_Sq): + return(12); + case(MDOC_Sy): + return(6); + case(MDOC_Sx): + return(16); + case(MDOC_Tn): + return(10); + case(MDOC_Va): + return(12); + case(MDOC_Vt): + return(12); + case(MDOC_Xr): + return(10); + default: + break; + }; + return(0); +} diff --git a/usr.bin/mandoc/mdoc_term.c b/usr.bin/mandoc/mdoc_term.c new file mode 100644 index 0000000000..7c2b91d667 --- /dev/null +++ b/usr.bin/mandoc/mdoc_term.c @@ -0,0 +1,2067 @@ +/* $Id: mdoc_term.c,v 1.61 2009/10/21 19:13:50 schwarze Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include + +#include +#include +#include +#include +#include +#include + +#include "out.h" +#include "term.h" +#include "mdoc.h" +#include "chars.h" +#include "main.h" + +#define INDENT 5 +#define HALFINDENT 3 + +struct termpair { + struct termpair *ppair; + int flag; + int count; +}; + +#define DECL_ARGS struct termp *p, \ + struct termpair *pair, \ + const struct mdoc_meta *m, \ + const struct mdoc_node *n + +struct termact { + int (*pre)(DECL_ARGS); + void (*post)(DECL_ARGS); +}; + +static void termp____post(DECL_ARGS); +static void termp_an_post(DECL_ARGS); +static void termp_aq_post(DECL_ARGS); +static void termp_bd_post(DECL_ARGS); +static void termp_bl_post(DECL_ARGS); +static void termp_bq_post(DECL_ARGS); +static void termp_brq_post(DECL_ARGS); +static void termp_bx_post(DECL_ARGS); +static void termp_d1_post(DECL_ARGS); +static void termp_dq_post(DECL_ARGS); +static void termp_fd_post(DECL_ARGS); +static void termp_fn_post(DECL_ARGS); +static void termp_fo_post(DECL_ARGS); +static void termp_ft_post(DECL_ARGS); +static void termp_in_post(DECL_ARGS); +static void termp_it_post(DECL_ARGS); +static void termp_lb_post(DECL_ARGS); +static void termp_op_post(DECL_ARGS); +static void termp_pf_post(DECL_ARGS); +static void termp_pq_post(DECL_ARGS); +static void termp_qq_post(DECL_ARGS); +static void termp_sh_post(DECL_ARGS); +static void termp_sq_post(DECL_ARGS); +static void termp_ss_post(DECL_ARGS); +static void termp_vt_post(DECL_ARGS); + +static int termp__t_pre(DECL_ARGS); +static int termp_an_pre(DECL_ARGS); +static int termp_ap_pre(DECL_ARGS); +static int termp_aq_pre(DECL_ARGS); +static int termp_bd_pre(DECL_ARGS); +static int termp_bf_pre(DECL_ARGS); +static int termp_bold_pre(DECL_ARGS); +static int termp_bq_pre(DECL_ARGS); +static int termp_brq_pre(DECL_ARGS); +static int termp_bt_pre(DECL_ARGS); +static int termp_cd_pre(DECL_ARGS); +static int termp_d1_pre(DECL_ARGS); +static int termp_dq_pre(DECL_ARGS); +static int termp_ex_pre(DECL_ARGS); +static int termp_fa_pre(DECL_ARGS); +static int termp_fl_pre(DECL_ARGS); +static int termp_fn_pre(DECL_ARGS); +static int termp_fo_pre(DECL_ARGS); +static int termp_ft_pre(DECL_ARGS); +static int termp_in_pre(DECL_ARGS); +static int termp_it_pre(DECL_ARGS); +static int termp_lk_pre(DECL_ARGS); +static int termp_nd_pre(DECL_ARGS); +static int termp_nm_pre(DECL_ARGS); +static int termp_ns_pre(DECL_ARGS); +static int termp_op_pre(DECL_ARGS); +static int termp_pf_pre(DECL_ARGS); +static int termp_pq_pre(DECL_ARGS); +static int termp_qq_pre(DECL_ARGS); +static int termp_rs_pre(DECL_ARGS); +static int termp_rv_pre(DECL_ARGS); +static int termp_sh_pre(DECL_ARGS); +static int termp_sm_pre(DECL_ARGS); +static int termp_sp_pre(DECL_ARGS); +static int termp_sq_pre(DECL_ARGS); +static int termp_ss_pre(DECL_ARGS); +static int termp_under_pre(DECL_ARGS); +static int termp_ud_pre(DECL_ARGS); +static int termp_xr_pre(DECL_ARGS); +static int termp_xx_pre(DECL_ARGS); + +static const struct termact termacts[MDOC_MAX] = { + { termp_ap_pre, NULL }, /* Ap */ + { NULL, NULL }, /* Dd */ + { NULL, NULL }, /* Dt */ + { NULL, NULL }, /* Os */ + { termp_sh_pre, termp_sh_post }, /* Sh */ + { termp_ss_pre, termp_ss_post }, /* Ss */ + { termp_sp_pre, NULL }, /* Pp */ + { termp_d1_pre, termp_d1_post }, /* D1 */ + { termp_d1_pre, termp_d1_post }, /* Dl */ + { termp_bd_pre, termp_bd_post }, /* Bd */ + { NULL, NULL }, /* Ed */ + { NULL, termp_bl_post }, /* Bl */ + { NULL, NULL }, /* El */ + { termp_it_pre, termp_it_post }, /* It */ + { NULL, NULL }, /* Ad */ + { termp_an_pre, termp_an_post }, /* An */ + { termp_under_pre, NULL }, /* Ar */ + { termp_cd_pre, NULL }, /* Cd */ + { termp_bold_pre, NULL }, /* Cm */ + { NULL, NULL }, /* Dv */ + { NULL, NULL }, /* Er */ + { NULL, NULL }, /* Ev */ + { termp_ex_pre, NULL }, /* Ex */ + { termp_fa_pre, NULL }, /* Fa */ + { termp_bold_pre, termp_fd_post }, /* Fd */ + { termp_fl_pre, NULL }, /* Fl */ + { termp_fn_pre, termp_fn_post }, /* Fn */ + { termp_ft_pre, termp_ft_post }, /* Ft */ + { termp_bold_pre, NULL }, /* Ic */ + { termp_in_pre, termp_in_post }, /* In */ + { NULL, NULL }, /* Li */ + { termp_nd_pre, NULL }, /* Nd */ + { termp_nm_pre, NULL }, /* Nm */ + { termp_op_pre, termp_op_post }, /* Op */ + { NULL, NULL }, /* Ot */ + { termp_under_pre, NULL }, /* Pa */ + { termp_rv_pre, NULL }, /* Rv */ + { NULL, NULL }, /* St */ + { termp_under_pre, NULL }, /* Va */ + { termp_under_pre, termp_vt_post }, /* Vt */ + { termp_xr_pre, NULL }, /* Xr */ + { NULL, termp____post }, /* %A */ + { termp_under_pre, termp____post }, /* %B */ + { NULL, termp____post }, /* %D */ + { termp_under_pre, termp____post }, /* %I */ + { termp_under_pre, termp____post }, /* %J */ + { NULL, termp____post }, /* %N */ + { NULL, termp____post }, /* %O */ + { NULL, termp____post }, /* %P */ + { NULL, termp____post }, /* %R */ + { termp__t_pre, termp____post }, /* %T */ + { NULL, termp____post }, /* %V */ + { NULL, NULL }, /* Ac */ + { termp_aq_pre, termp_aq_post }, /* Ao */ + { termp_aq_pre, termp_aq_post }, /* Aq */ + { NULL, NULL }, /* At */ + { NULL, NULL }, /* Bc */ + { termp_bf_pre, NULL }, /* Bf */ + { termp_bq_pre, termp_bq_post }, /* Bo */ + { termp_bq_pre, termp_bq_post }, /* Bq */ + { termp_xx_pre, NULL }, /* Bsx */ + { NULL, termp_bx_post }, /* Bx */ + { NULL, NULL }, /* Db */ + { NULL, NULL }, /* Dc */ + { termp_dq_pre, termp_dq_post }, /* Do */ + { termp_dq_pre, termp_dq_post }, /* Dq */ + { NULL, NULL }, /* Ec */ + { NULL, NULL }, /* Ef */ + { termp_under_pre, NULL }, /* Em */ + { NULL, NULL }, /* Eo */ + { termp_xx_pre, NULL }, /* Fx */ + { termp_bold_pre, NULL }, /* Ms */ /* FIXME: convert to symbol? */ + { NULL, NULL }, /* No */ + { termp_ns_pre, NULL }, /* Ns */ + { termp_xx_pre, NULL }, /* Nx */ + { termp_xx_pre, NULL }, /* Ox */ + { NULL, NULL }, /* Pc */ + { termp_pf_pre, termp_pf_post }, /* Pf */ + { termp_pq_pre, termp_pq_post }, /* Po */ + { termp_pq_pre, termp_pq_post }, /* Pq */ + { NULL, NULL }, /* Qc */ + { termp_sq_pre, termp_sq_post }, /* Ql */ + { termp_qq_pre, termp_qq_post }, /* Qo */ + { termp_qq_pre, termp_qq_post }, /* Qq */ + { NULL, NULL }, /* Re */ + { termp_rs_pre, NULL }, /* Rs */ + { NULL, NULL }, /* Sc */ + { termp_sq_pre, termp_sq_post }, /* So */ + { termp_sq_pre, termp_sq_post }, /* Sq */ + { termp_sm_pre, NULL }, /* Sm */ + { termp_under_pre, NULL }, /* Sx */ + { termp_bold_pre, NULL }, /* Sy */ + { NULL, NULL }, /* Tn */ + { termp_xx_pre, NULL }, /* Ux */ + { NULL, NULL }, /* Xc */ + { NULL, NULL }, /* Xo */ + { termp_fo_pre, termp_fo_post }, /* Fo */ + { NULL, NULL }, /* Fc */ + { termp_op_pre, termp_op_post }, /* Oo */ + { NULL, NULL }, /* Oc */ + { NULL, NULL }, /* Bk */ + { NULL, NULL }, /* Ek */ + { termp_bt_pre, NULL }, /* Bt */ + { NULL, NULL }, /* Hf */ + { NULL, NULL }, /* Fr */ + { termp_ud_pre, NULL }, /* Ud */ + { NULL, termp_lb_post }, /* Lb */ + { termp_sp_pre, NULL }, /* Lp */ + { termp_lk_pre, NULL }, /* Lk */ + { termp_under_pre, NULL }, /* Mt */ + { termp_brq_pre, termp_brq_post }, /* Brq */ + { termp_brq_pre, termp_brq_post }, /* Bro */ + { NULL, NULL }, /* Brc */ + { NULL, termp____post }, /* %C */ + { NULL, NULL }, /* Es */ /* TODO */ + { NULL, NULL }, /* En */ /* TODO */ + { termp_xx_pre, NULL }, /* Dx */ + { NULL, termp____post }, /* %Q */ + { termp_sp_pre, NULL }, /* br */ + { termp_sp_pre, NULL }, /* sp */ +}; + +static size_t arg2width(const struct mdoc_argv *, int); +static size_t arg2height(const struct mdoc_node *); +static size_t arg2offs(const struct mdoc_argv *); + +static int arg_hasattr(int, const struct mdoc_node *); +static int arg_getattrs(const int *, int *, size_t, + const struct mdoc_node *); +static int arg_getattr(int, const struct mdoc_node *); +static int arg_listtype(const struct mdoc_node *); +static void print_bvspace(struct termp *, + const struct mdoc_node *, + const struct mdoc_node *); +static void print_node(DECL_ARGS); +static void print_head(DECL_ARGS); +static void print_body(DECL_ARGS); +static void print_foot(DECL_ARGS); + + +void +terminal_mdoc(void *arg, const struct mdoc *mdoc) +{ + const struct mdoc_node *n; + const struct mdoc_meta *m; + struct termp *p; + + p = (struct termp *)arg; + + if (NULL == p->symtab) + switch (p->enc) { + case (TERMENC_ASCII): + p->symtab = chars_init(CHARS_ASCII); + break; + default: + abort(); + /* NOTREACHED */ + } + + n = mdoc_node(mdoc); + m = mdoc_meta(mdoc); + + print_head(p, NULL, m, n); + if (n->child) + print_body(p, NULL, m, n->child); + print_foot(p, NULL, m, n); +} + + +static void +print_body(DECL_ARGS) +{ + + print_node(p, pair, m, n); + if (n->next) + print_body(p, pair, m, n->next); +} + + +/* ARGSUSED */ +static void +print_node(DECL_ARGS) +{ + int chld, bold, under; + struct termpair npair; + size_t offset, rmargin; + + chld = 1; + offset = p->offset; + rmargin = p->rmargin; + bold = p->bold; + under = p->under; + + bzero(&npair, sizeof(struct termpair)); + npair.ppair = pair; + + if (MDOC_TEXT != n->type) { + if (termacts[n->tok].pre) + chld = (*termacts[n->tok].pre)(p, &npair, m, n); + } else + term_word(p, n->string); + if (chld && n->child) + print_body(p, &npair, m, n->child); + + /* + * XXX - if bold/under were to span scopes, this wouldn't be + * possible, but because decoration is always in-scope, we can + * get away with this. + */ + + p->bold = bold; + p->under = under; + + if (MDOC_TEXT != n->type) + if (termacts[n->tok].post) + (*termacts[n->tok].post)(p, &npair, m, n); + + p->offset = offset; + p->rmargin = rmargin; +} + + +/* ARGSUSED */ +static void +print_foot(DECL_ARGS) +{ + struct tm *tm; + char *buf, *os; + + /* + * Output the footer in new-groff style, that is, three columns + * with the middle being the manual date and flanking columns + * being the operating system: + * + * SYSTEM DATE SYSTEM + */ + + if (NULL == (buf = malloc(p->rmargin))) + err(EXIT_FAILURE, "malloc"); + if (NULL == (os = malloc(p->rmargin))) + err(EXIT_FAILURE, "malloc"); + + tm = localtime(&m->date); + + if (0 == strftime(buf, p->rmargin, "%B %e, %Y", tm)) + err(EXIT_FAILURE, "strftime"); + + (void)strlcpy(os, m->os, p->rmargin); + + term_vspace(p); + + p->offset = 0; + p->rmargin = (p->maxrmargin - strlen(buf) + 1) / 2; + p->flags |= TERMP_NOSPACE | TERMP_NOBREAK; + + term_word(p, os); + term_flushln(p); + + p->offset = p->rmargin; + p->rmargin = p->maxrmargin - strlen(os); + p->flags |= TERMP_NOLPAD | TERMP_NOSPACE; + + term_word(p, buf); + term_flushln(p); + + p->offset = p->rmargin; + p->rmargin = p->maxrmargin; + p->flags &= ~TERMP_NOBREAK; + p->flags |= TERMP_NOLPAD | TERMP_NOSPACE; + + term_word(p, os); + term_flushln(p); + + p->offset = 0; + p->rmargin = p->maxrmargin; + p->flags = 0; + + free(buf); + free(os); +} + + +/* FIXME: put in utility library. */ +/* ARGSUSED */ +static void +print_head(DECL_ARGS) +{ + char *buf, *title; + + p->rmargin = p->maxrmargin; + p->offset = 0; + + if (NULL == (buf = malloc(p->rmargin))) + err(EXIT_FAILURE, "malloc"); + if (NULL == (title = malloc(p->rmargin))) + err(EXIT_FAILURE, "malloc"); + + /* + * The header is strange. It has three components, which are + * really two with the first duplicated. It goes like this: + * + * IDENTIFIER TITLE IDENTIFIER + * + * The IDENTIFIER is NAME(SECTION), which is the command-name + * (if given, or "unknown" if not) followed by the manual page + * section. These are given in `Dt'. The TITLE is a free-form + * string depending on the manual volume. If not specified, it + * switches on the manual section. + */ + + assert(m->vol); + (void)strlcpy(buf, m->vol, p->rmargin); + + if (m->arch) { + (void)strlcat(buf, " (", p->rmargin); + (void)strlcat(buf, m->arch, p->rmargin); + (void)strlcat(buf, ")", p->rmargin); + } + + snprintf(title, p->rmargin, "%s(%d)", m->title, m->msec); + + p->offset = 0; + p->rmargin = (p->maxrmargin - strlen(buf) + 1) / 2; + p->flags |= TERMP_NOBREAK | TERMP_NOSPACE; + + term_word(p, title); + term_flushln(p); + + p->offset = p->rmargin; + p->rmargin = p->maxrmargin - strlen(title); + p->flags |= TERMP_NOLPAD | TERMP_NOSPACE; + + term_word(p, buf); + term_flushln(p); + + p->offset = p->rmargin; + p->rmargin = p->maxrmargin; + p->flags &= ~TERMP_NOBREAK; + p->flags |= TERMP_NOLPAD | TERMP_NOSPACE; + + term_word(p, title); + term_flushln(p); + + p->offset = 0; + p->rmargin = p->maxrmargin; + p->flags &= ~TERMP_NOSPACE; + + free(title); + free(buf); +} + + +static size_t +arg2height(const struct mdoc_node *n) +{ + struct roffsu su; + + assert(MDOC_TEXT == n->type); + assert(n->string); + if ( ! a2roffsu(n->string, &su, SCALE_VS)) + SCALE_VS_INIT(&su, strlen(n->string)); + + return(term_vspan(&su)); +} + + +static size_t +arg2width(const struct mdoc_argv *arg, int pos) +{ + struct roffsu su; + + assert(arg->value[pos]); + if ( ! a2roffsu(arg->value[pos], &su, SCALE_MAX)) + SCALE_HS_INIT(&su, strlen(arg->value[pos])); + + /* XXX: pachemu? */ + return(term_hspan(&su) + 2); +} + + +static int +arg_listtype(const struct mdoc_node *n) +{ + int i, len; + + assert(MDOC_BLOCK == n->type); + + len = (int)(n->args ? n->args->argc : 0); + + for (i = 0; i < len; i++) + switch (n->args->argv[i].arg) { + case (MDOC_Bullet): + /* FALLTHROUGH */ + case (MDOC_Dash): + /* FALLTHROUGH */ + case (MDOC_Enum): + /* FALLTHROUGH */ + case (MDOC_Hyphen): + /* FALLTHROUGH */ + case (MDOC_Tag): + /* FALLTHROUGH */ + case (MDOC_Inset): + /* FALLTHROUGH */ + case (MDOC_Diag): + /* FALLTHROUGH */ + case (MDOC_Item): + /* FALLTHROUGH */ + case (MDOC_Column): + /* FALLTHROUGH */ + case (MDOC_Hang): + /* FALLTHROUGH */ + case (MDOC_Ohang): + return(n->args->argv[i].arg); + default: + break; + } + + return(-1); +} + + +static size_t +arg2offs(const struct mdoc_argv *arg) +{ + struct roffsu su; + + if ('\0' == arg->value[0][0]) + return(0); + else if (0 == strcmp(arg->value[0], "left")) + return(0); + else if (0 == strcmp(arg->value[0], "indent")) + return(INDENT + 1); + else if (0 == strcmp(arg->value[0], "indent-two")) + return((INDENT + 1) * 2); + else if ( ! a2roffsu(arg->value[0], &su, SCALE_MAX)) + SCALE_HS_INIT(&su, strlen(arg->value[0])); + + return(term_hspan(&su)); +} + + +static int +arg_hasattr(int arg, const struct mdoc_node *n) +{ + + return(-1 != arg_getattr(arg, n)); +} + + +static int +arg_getattr(int v, const struct mdoc_node *n) +{ + int val; + + return(arg_getattrs(&v, &val, 1, n) ? val : -1); +} + + +static int +arg_getattrs(const int *keys, int *vals, + size_t sz, const struct mdoc_node *n) +{ + int i, j, k; + + if (NULL == n->args) + return(0); + + for (k = i = 0; i < (int)n->args->argc; i++) + for (j = 0; j < (int)sz; j++) + if (n->args->argv[i].arg == keys[j]) { + vals[j] = i; + k++; + } + return(k); +} + + +static void +print_bvspace(struct termp *p, + const struct mdoc_node *bl, + const struct mdoc_node *n) +{ + const struct mdoc_node *nn; + + term_newln(p); + if (arg_hasattr(MDOC_Compact, bl)) + return; + + /* Do not vspace directly after Ss/Sh. */ + + for (nn = n; nn; nn = nn->parent) { + if (MDOC_BLOCK != nn->type) + continue; + if (MDOC_Ss == nn->tok) + return; + if (MDOC_Sh == nn->tok) + return; + if (NULL == nn->prev) + continue; + break; + } + + /* A `-column' does not assert vspace within the list. */ + + if (MDOC_Bl == bl->tok && arg_hasattr(MDOC_Column, bl)) + if (n->prev && MDOC_It == n->prev->tok) + return; + + /* A `-diag' without body does not vspace. */ + + if (MDOC_Bl == bl->tok && arg_hasattr(MDOC_Diag, bl)) + if (n->prev && MDOC_It == n->prev->tok) { + assert(n->prev->body); + if (NULL == n->prev->body->child) + return; + } + + term_vspace(p); +} + + +/* ARGSUSED */ +static int +termp_dq_pre(DECL_ARGS) +{ + + if (MDOC_BODY != n->type) + return(1); + + term_word(p, "\\(lq"); + p->flags |= TERMP_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static void +termp_dq_post(DECL_ARGS) +{ + + if (MDOC_BODY != n->type) + return; + + p->flags |= TERMP_NOSPACE; + term_word(p, "\\(rq"); +} + + +/* ARGSUSED */ +static int +termp_it_pre(DECL_ARGS) +{ + const struct mdoc_node *bl, *nn; + char buf[7]; + int i, type, keys[3], vals[3]; + size_t width, offset; + + if (MDOC_BLOCK == n->type) { + print_bvspace(p, n->parent->parent, n); + return(1); + } + + bl = n->parent->parent->parent; + + /* Save parent attributes. */ + + pair->flag = p->flags; + + /* Get list width and offset. */ + + keys[0] = MDOC_Width; + keys[1] = MDOC_Offset; + keys[2] = MDOC_Column; + + vals[0] = vals[1] = vals[2] = -1; + + width = offset = 0; + + (void)arg_getattrs(keys, vals, 3, bl); + + type = arg_listtype(bl); + assert(-1 != type); + + /* Calculate real width and offset. */ + + switch (type) { + case (MDOC_Column): + if (MDOC_BODY == n->type) + break; + /* + * Work around groff's column handling. The offset is + * equal to the sum of all widths leading to the current + * column (plus the -offset value). If this column + * exceeds the stated number of columns, the width is + * set as 0, else it's the stated column width (later + * the 0 will be adjusted to default 10 or, if in the + * last column case, set to stretch to the margin). + */ + for (i = 0, nn = n->prev; nn && + i < (int)bl->args->argv[vals[2]].sz; + nn = nn->prev, i++) + offset += arg2width + (&bl->args->argv[vals[2]], i); + + /* Whether exceeds maximum column. */ + if (i < (int)bl->args->argv[vals[2]].sz) + width = arg2width(&bl->args->argv[vals[2]], i); + else + width = 0; + + if (vals[1] >= 0) + offset += arg2offs(&bl->args->argv[vals[1]]); + break; + default: + if (vals[0] >= 0) + width = arg2width(&bl->args->argv[vals[0]], 0); + if (vals[1] >= 0) + offset += arg2offs(&bl->args->argv[vals[1]]); + break; + } + + /* + * List-type can override the width in the case of fixed-head + * values (bullet, dash/hyphen, enum). Tags need a non-zero + * offset. + */ + + switch (type) { + case (MDOC_Bullet): + /* FALLTHROUGH */ + case (MDOC_Dash): + /* FALLTHROUGH */ + case (MDOC_Hyphen): + if (width < 4) + width = 4; + break; + case (MDOC_Enum): + if (width < 5) + width = 5; + break; + case (MDOC_Hang): + if (0 == width) + width = 8; + break; + case (MDOC_Column): + /* FALLTHROUGH */ + case (MDOC_Tag): + if (0 == width) + width = 10; + break; + default: + break; + } + + /* + * Whitespace control. Inset bodies need an initial space, + * while diagonal bodies need two. + */ + + p->flags |= TERMP_NOSPACE; + + switch (type) { + case (MDOC_Diag): + if (MDOC_BODY == n->type) + term_word(p, "\\ \\ "); + break; + case (MDOC_Inset): + if (MDOC_BODY == n->type) + term_word(p, "\\ "); + break; + default: + break; + } + + p->flags |= TERMP_NOSPACE; + + switch (type) { + case (MDOC_Diag): + if (MDOC_HEAD == n->type) + p->bold++; + break; + default: + break; + } + + /* + * Pad and break control. This is the tricker part. Lists with + * set right-margins for the head get TERMP_NOBREAK because, if + * they overrun the margin, they wrap to the new margin. + * Correspondingly, the body for these types don't left-pad, as + * the head will pad out to to the right. + */ + + switch (type) { + case (MDOC_Bullet): + /* FALLTHROUGH */ + case (MDOC_Dash): + /* FALLTHROUGH */ + case (MDOC_Enum): + /* FALLTHROUGH */ + case (MDOC_Hyphen): + if (MDOC_HEAD == n->type) + p->flags |= TERMP_NOBREAK; + else + p->flags |= TERMP_NOLPAD; + break; + case (MDOC_Hang): + if (MDOC_HEAD == n->type) + p->flags |= TERMP_NOBREAK; + else + p->flags |= TERMP_NOLPAD; + + if (MDOC_HEAD != n->type) + break; + + /* + * This is ugly. If `-hang' is specified and the body + * is a `Bl' or `Bd', then we want basically to nullify + * the "overstep" effect in term_flushln() and treat + * this as a `-ohang' list instead. + */ + if (n->next->child && + (MDOC_Bl == n->next->child->tok || + MDOC_Bd == n->next->child->tok)) { + p->flags &= ~TERMP_NOBREAK; + p->flags &= ~TERMP_NOLPAD; + } else + p->flags |= TERMP_HANG; + break; + case (MDOC_Tag): + if (MDOC_HEAD == n->type) + p->flags |= TERMP_NOBREAK | TERMP_TWOSPACE; + else + p->flags |= TERMP_NOLPAD; + + if (MDOC_HEAD != n->type) + break; + if (NULL == n->next || NULL == n->next->child) + p->flags |= TERMP_DANGLE; + break; + case (MDOC_Column): + if (MDOC_HEAD == n->type) { + assert(n->next); + if (MDOC_BODY == n->next->type) + p->flags &= ~TERMP_NOBREAK; + else + p->flags |= TERMP_NOBREAK; + if (n->prev) + p->flags |= TERMP_NOLPAD; + } + break; + case (MDOC_Diag): + if (MDOC_HEAD == n->type) + p->flags |= TERMP_NOBREAK; + break; + default: + break; + } + + /* + * Margin control. Set-head-width lists have their right + * margins shortened. The body for these lists has the offset + * necessarily lengthened. Everybody gets the offset. + */ + + p->offset += offset; + + switch (type) { + case (MDOC_Hang): + /* + * Same stipulation as above, regarding `-hang'. We + * don't want to recalculate rmargin and offsets when + * using `Bd' or `Bl' within `-hang' overstep lists. + */ + if (MDOC_HEAD == n->type && n->next->child && + (MDOC_Bl == n->next->child->tok || + MDOC_Bd == n->next->child->tok)) + break; + /* FALLTHROUGH */ + case (MDOC_Bullet): + /* FALLTHROUGH */ + case (MDOC_Dash): + /* FALLTHROUGH */ + case (MDOC_Enum): + /* FALLTHROUGH */ + case (MDOC_Hyphen): + /* FALLTHROUGH */ + case (MDOC_Tag): + assert(width); + if (MDOC_HEAD == n->type) + p->rmargin = p->offset + width; + else + p->offset += width; + break; + case (MDOC_Column): + assert(width); + p->rmargin = p->offset + width; + /* + * XXX - this behaviour is not documented: the + * right-most column is filled to the right margin. + */ + if (MDOC_HEAD == n->type && + MDOC_BODY == n->next->type) + p->rmargin = p->maxrmargin; + break; + default: + break; + } + + /* + * The dash, hyphen, bullet and enum lists all have a special + * HEAD character (temporarily bold, in some cases). + */ + + if (MDOC_HEAD == n->type) + switch (type) { + case (MDOC_Bullet): + p->bold++; + term_word(p, "\\[bu]"); + p->bold--; + break; + case (MDOC_Dash): + /* FALLTHROUGH */ + case (MDOC_Hyphen): + p->bold++; + term_word(p, "\\(hy"); + p->bold--; + break; + case (MDOC_Enum): + (pair->ppair->ppair->count)++; + (void)snprintf(buf, sizeof(buf), "%d.", + pair->ppair->ppair->count); + term_word(p, buf); + break; + default: + break; + } + + /* + * If we're not going to process our children, indicate so here. + */ + + switch (type) { + case (MDOC_Bullet): + /* FALLTHROUGH */ + case (MDOC_Item): + /* FALLTHROUGH */ + case (MDOC_Dash): + /* FALLTHROUGH */ + case (MDOC_Hyphen): + /* FALLTHROUGH */ + case (MDOC_Enum): + if (MDOC_HEAD == n->type) + return(0); + break; + case (MDOC_Column): + if (MDOC_BODY == n->type) + return(0); + break; + default: + break; + } + + return(1); +} + + +/* ARGSUSED */ +static void +termp_it_post(DECL_ARGS) +{ + int type; + + if (MDOC_BODY != n->type && MDOC_HEAD != n->type) + return; + + type = arg_listtype(n->parent->parent->parent); + assert(-1 != type); + + switch (type) { + case (MDOC_Item): + /* FALLTHROUGH */ + case (MDOC_Diag): + /* FALLTHROUGH */ + case (MDOC_Inset): + if (MDOC_BODY == n->type) + term_flushln(p); + break; + case (MDOC_Column): + if (MDOC_HEAD == n->type) + term_flushln(p); + break; + default: + term_flushln(p); + break; + } + + p->flags = pair->flag; +} + + +/* ARGSUSED */ +static int +termp_nm_pre(DECL_ARGS) +{ + + if (SEC_SYNOPSIS == n->sec) + term_newln(p); + p->bold++; + if (NULL == n->child) + term_word(p, m->name); + return(1); +} + + +/* ARGSUSED */ +static int +termp_fl_pre(DECL_ARGS) +{ + + p->bold++; + term_word(p, "\\-"); + p->flags |= TERMP_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static int +termp_an_pre(DECL_ARGS) +{ + + if (NULL == n->child) + return(1); + + /* + * If not in the AUTHORS section, `An -split' will cause + * newlines to occur before the author name. If in the AUTHORS + * section, by default, the first `An' invocation is nosplit, + * then all subsequent ones, regardless of whether interspersed + * with other macros/text, are split. -split, in this case, + * will override the condition of the implied first -nosplit. + */ + + if (n->sec == SEC_AUTHORS) { + if ( ! (TERMP_ANPREC & p->flags)) { + if (TERMP_SPLIT & p->flags) + term_newln(p); + return(1); + } + if (TERMP_NOSPLIT & p->flags) + return(1); + term_newln(p); + return(1); + } + + if (TERMP_SPLIT & p->flags) + term_newln(p); + + return(1); +} + + +/* ARGSUSED */ +static void +termp_an_post(DECL_ARGS) +{ + + if (n->child) { + if (SEC_AUTHORS == n->sec) + p->flags |= TERMP_ANPREC; + return; + } + + if (arg_getattr(MDOC_Split, n) > -1) { + p->flags &= ~TERMP_NOSPLIT; + p->flags |= TERMP_SPLIT; + } else { + p->flags &= ~TERMP_SPLIT; + p->flags |= TERMP_NOSPLIT; + } + +} + + +/* ARGSUSED */ +static int +termp_ns_pre(DECL_ARGS) +{ + + p->flags |= TERMP_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static int +termp_rs_pre(DECL_ARGS) +{ + + if (SEC_SEE_ALSO != n->sec) + return(1); + if (MDOC_BLOCK == n->type && n->prev) + term_vspace(p); + return(1); +} + + +/* ARGSUSED */ +static int +termp_rv_pre(DECL_ARGS) +{ + const struct mdoc_node *nn; + + term_newln(p); + term_word(p, "The"); + + for (nn = n->child; nn; nn = nn->next) { + p->bold++; + term_word(p, nn->string); + p->bold--; + p->flags |= TERMP_NOSPACE; + if (nn->next && NULL == nn->next->next) + term_word(p, "(), and"); + else if (nn->next) + term_word(p, "(),"); + else + term_word(p, "()"); + } + + if (n->child->next) + term_word(p, "functions return"); + else + term_word(p, "function returns"); + + term_word(p, "the value 0 if successful; otherwise the value " + "-1 is returned and the global variable"); + + p->under++; + term_word(p, "errno"); + p->under--; + + term_word(p, "is set to indicate the error."); + + return(0); +} + + +/* ARGSUSED */ +static int +termp_ex_pre(DECL_ARGS) +{ + const struct mdoc_node *nn; + + term_word(p, "The"); + + for (nn = n->child; nn; nn = nn->next) { + p->bold++; + term_word(p, nn->string); + p->bold--; + p->flags |= TERMP_NOSPACE; + if (nn->next && NULL == nn->next->next) + term_word(p, ", and"); + else if (nn->next) + term_word(p, ","); + else + p->flags &= ~TERMP_NOSPACE; + } + + if (n->child->next) + term_word(p, "utilities exit"); + else + term_word(p, "utility exits"); + + term_word(p, "0 on success, and >0 if an error occurs."); + + return(0); +} + + +/* ARGSUSED */ +static int +termp_nd_pre(DECL_ARGS) +{ + + if (MDOC_BODY != n->type) + return(1); + +#if defined(__OpenBSD__) || defined(__linux__) + term_word(p, "\\(en"); +#else + term_word(p, "\\(em"); +#endif + return(1); +} + + +/* ARGSUSED */ +static void +termp_bl_post(DECL_ARGS) +{ + + if (MDOC_BLOCK == n->type) + term_newln(p); +} + + +/* ARGSUSED */ +static void +termp_op_post(DECL_ARGS) +{ + + if (MDOC_BODY != n->type) + return; + p->flags |= TERMP_NOSPACE; + term_word(p, "\\(rB"); +} + + +/* ARGSUSED */ +static int +termp_xr_pre(DECL_ARGS) +{ + const struct mdoc_node *nn; + + assert(n->child && MDOC_TEXT == n->child->type); + nn = n->child; + + term_word(p, nn->string); + if (NULL == (nn = nn->next)) + return(0); + p->flags |= TERMP_NOSPACE; + term_word(p, "("); + p->flags |= TERMP_NOSPACE; + term_word(p, nn->string); + p->flags |= TERMP_NOSPACE; + term_word(p, ")"); + + return(0); +} + + +/* ARGSUSED */ +static void +termp_vt_post(DECL_ARGS) +{ + + if (n->sec != SEC_SYNOPSIS) + return; + if (n->next && MDOC_Vt == n->next->tok) + term_newln(p); + else if (n->next) + term_vspace(p); +} + + +/* ARGSUSED */ +static int +termp_bold_pre(DECL_ARGS) +{ + + p->bold++; + return(1); +} + + +/* ARGSUSED */ +static void +termp_fd_post(DECL_ARGS) +{ + + if (n->sec != SEC_SYNOPSIS) + return; + + term_newln(p); + if (n->next && MDOC_Fd != n->next->tok) + term_vspace(p); +} + + +/* ARGSUSED */ +static int +termp_sh_pre(DECL_ARGS) +{ + + /* No vspace between consecutive `Sh' calls. */ + + switch (n->type) { + case (MDOC_BLOCK): + if (n->prev && MDOC_Sh == n->prev->tok) + if (NULL == n->prev->body->child) + break; + term_vspace(p); + break; + case (MDOC_HEAD): + p->bold++; + break; + case (MDOC_BODY): + p->offset = INDENT; + break; + default: + break; + } + return(1); +} + + +/* ARGSUSED */ +static void +termp_sh_post(DECL_ARGS) +{ + + switch (n->type) { + case (MDOC_HEAD): + term_newln(p); + break; + case (MDOC_BODY): + term_newln(p); + p->offset = 0; + break; + default: + break; + } +} + + +/* ARGSUSED */ +static int +termp_op_pre(DECL_ARGS) +{ + + switch (n->type) { + case (MDOC_BODY): + term_word(p, "\\(lB"); + p->flags |= TERMP_NOSPACE; + break; + default: + break; + } + return(1); +} + + +/* ARGSUSED */ +static int +termp_bt_pre(DECL_ARGS) +{ + + term_word(p, "is currently in beta test."); + return(0); +} + + +/* ARGSUSED */ +static void +termp_lb_post(DECL_ARGS) +{ + + if (SEC_LIBRARY == n->sec) + term_newln(p); +} + + +/* ARGSUSED */ +static int +termp_ud_pre(DECL_ARGS) +{ + + term_word(p, "currently under development."); + return(0); +} + + +/* ARGSUSED */ +static int +termp_d1_pre(DECL_ARGS) +{ + + if (MDOC_BLOCK != n->type) + return(1); + term_newln(p); + p->offset += (INDENT + 1); + return(1); +} + + +/* ARGSUSED */ +static void +termp_d1_post(DECL_ARGS) +{ + + if (MDOC_BLOCK != n->type) + return; + term_newln(p); +} + + +/* ARGSUSED */ +static int +termp_aq_pre(DECL_ARGS) +{ + + if (MDOC_BODY != n->type) + return(1); + term_word(p, "\\(la"); + p->flags |= TERMP_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static void +termp_aq_post(DECL_ARGS) +{ + + if (MDOC_BODY != n->type) + return; + p->flags |= TERMP_NOSPACE; + term_word(p, "\\(ra"); +} + + +/* ARGSUSED */ +static int +termp_ft_pre(DECL_ARGS) +{ + + if (SEC_SYNOPSIS == n->sec) + if (n->prev && MDOC_Fo == n->prev->tok) + term_vspace(p); + p->under++; + return(1); +} + + +/* ARGSUSED */ +static void +termp_ft_post(DECL_ARGS) +{ + + if (SEC_SYNOPSIS == n->sec) + term_newln(p); +} + + +/* ARGSUSED */ +static int +termp_fn_pre(DECL_ARGS) +{ + const struct mdoc_node *nn; + + p->bold++; + term_word(p, n->child->string); + p->bold--; + + p->flags |= TERMP_NOSPACE; + term_word(p, "("); + + for (nn = n->child->next; nn; nn = nn->next) { + p->under++; + term_word(p, nn->string); + p->under--; + if (nn->next) + term_word(p, ","); + } + + term_word(p, ")"); + + if (SEC_SYNOPSIS == n->sec) + term_word(p, ";"); + + return(0); +} + + +/* ARGSUSED */ +static void +termp_fn_post(DECL_ARGS) +{ + + if (n->sec == SEC_SYNOPSIS && n->next) + term_vspace(p); +} + + +/* ARGSUSED */ +static int +termp_fa_pre(DECL_ARGS) +{ + const struct mdoc_node *nn; + + if (n->parent->tok != MDOC_Fo) { + p->under++; + return(1); + } + + for (nn = n->child; nn; nn = nn->next) { + p->under++; + term_word(p, nn->string); + p->under--; + if (nn->next) + term_word(p, ","); + } + + if (n->child && n->next && n->next->tok == MDOC_Fa) + term_word(p, ","); + + return(0); +} + + +/* ARGSUSED */ +static int +termp_bd_pre(DECL_ARGS) +{ + int i, type; + const struct mdoc_node *nn; + + if (MDOC_BLOCK == n->type) { + print_bvspace(p, n, n); + return(1); + } else if (MDOC_BODY != n->type) + return(1); + + nn = n->parent; + + for (type = -1, i = 0; i < (int)nn->args->argc; i++) { + switch (nn->args->argv[i].arg) { + case (MDOC_Centred): + /* FALLTHROUGH */ + case (MDOC_Ragged): + /* FALLTHROUGH */ + case (MDOC_Filled): + /* FALLTHROUGH */ + case (MDOC_Unfilled): + /* FALLTHROUGH */ + case (MDOC_Literal): + type = nn->args->argv[i].arg; + break; + case (MDOC_Offset): + p->offset += arg2offs(&nn->args->argv[i]); + break; + default: + break; + } + } + + /* + * If -ragged or -filled are specified, the block does nothing + * but change the indentation. If -unfilled or -literal are + * specified, text is printed exactly as entered in the display: + * for macro lines, a newline is appended to the line. Blank + * lines are allowed. + */ + + assert(type > -1); + if (MDOC_Literal != type && MDOC_Unfilled != type) + return(1); + + for (nn = n->child; nn; nn = nn->next) { + p->flags |= TERMP_NOSPACE; + print_node(p, pair, m, nn); + if (NULL == nn->next) + continue; + if (nn->prev && nn->prev->line < nn->line) + term_flushln(p); + else if (NULL == nn->prev) + term_flushln(p); + } + + return(0); +} + + +/* ARGSUSED */ +static void +termp_bd_post(DECL_ARGS) +{ + + if (MDOC_BODY != n->type) + return; + p->flags |= TERMP_NOSPACE; + term_flushln(p); +} + + +/* ARGSUSED */ +static int +termp_qq_pre(DECL_ARGS) +{ + + if (MDOC_BODY != n->type) + return(1); + term_word(p, "\""); + p->flags |= TERMP_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static void +termp_qq_post(DECL_ARGS) +{ + + if (MDOC_BODY != n->type) + return; + p->flags |= TERMP_NOSPACE; + term_word(p, "\""); +} + + +/* ARGSUSED */ +static void +termp_bx_post(DECL_ARGS) +{ + + if (n->child) + p->flags |= TERMP_NOSPACE; + term_word(p, "BSD"); +} + + +/* ARGSUSED */ +static int +termp_xx_pre(DECL_ARGS) +{ + const char *pp; + + pp = NULL; + switch (n->tok) { + case (MDOC_Bsx): + pp = "BSDI BSD/OS"; + break; + case (MDOC_Dx): + pp = "DragonFlyBSD"; + break; + case (MDOC_Fx): + pp = "FreeBSD"; + break; + case (MDOC_Nx): + pp = "NetBSD"; + break; + case (MDOC_Ox): + pp = "OpenBSD"; + break; + case (MDOC_Ux): + pp = "UNIX"; + break; + default: + break; + } + + assert(pp); + term_word(p, pp); + return(1); +} + + +/* ARGSUSED */ +static int +termp_sq_pre(DECL_ARGS) +{ + + if (MDOC_BODY != n->type) + return(1); + term_word(p, "\\(oq"); + p->flags |= TERMP_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static void +termp_sq_post(DECL_ARGS) +{ + + if (MDOC_BODY != n->type) + return; + p->flags |= TERMP_NOSPACE; + term_word(p, "\\(aq"); +} + + +/* ARGSUSED */ +static int +termp_pf_pre(DECL_ARGS) +{ + + p->flags |= TERMP_IGNDELIM; + return(1); +} + + +/* ARGSUSED */ +static void +termp_pf_post(DECL_ARGS) +{ + + p->flags &= ~TERMP_IGNDELIM; + p->flags |= TERMP_NOSPACE; +} + + +/* ARGSUSED */ +static int +termp_ss_pre(DECL_ARGS) +{ + + switch (n->type) { + case (MDOC_BLOCK): + term_newln(p); + if (n->prev) + term_vspace(p); + break; + case (MDOC_HEAD): + p->bold++; + p->offset = HALFINDENT; + break; + default: + break; + } + + return(1); +} + + +/* ARGSUSED */ +static void +termp_ss_post(DECL_ARGS) +{ + + if (MDOC_HEAD == n->type) + term_newln(p); +} + + +/* ARGSUSED */ +static int +termp_cd_pre(DECL_ARGS) +{ + + p->bold++; + term_newln(p); + return(1); +} + + +/* ARGSUSED */ +static int +termp_in_pre(DECL_ARGS) +{ + + p->bold++; + if (SEC_SYNOPSIS == n->sec) + term_word(p, "#include"); + + term_word(p, "<"); + p->flags |= TERMP_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static void +termp_in_post(DECL_ARGS) +{ + + p->bold++; + p->flags |= TERMP_NOSPACE; + term_word(p, ">"); + p->bold--; + + if (SEC_SYNOPSIS != n->sec) + return; + + term_newln(p); + /* + * XXX Not entirely correct. If `.In foo bar' is specified in + * the SYNOPSIS section, then it produces a single break after + * the ; mandoc asserts a vertical space. Since this + * construction is rarely used, I think it's fine. + */ + if (n->next && MDOC_In != n->next->tok) + term_vspace(p); +} + + +/* ARGSUSED */ +static int +termp_sp_pre(DECL_ARGS) +{ + size_t i, len; + + switch (n->tok) { + case (MDOC_sp): + len = n->child ? arg2height(n->child) : 1; + break; + case (MDOC_br): + len = 0; + break; + default: + len = 1; + break; + } + + if (0 == len) + term_newln(p); + for (i = 0; i < len; i++) + term_vspace(p); + + return(0); +} + + +/* ARGSUSED */ +static int +termp_brq_pre(DECL_ARGS) +{ + + if (MDOC_BODY != n->type) + return(1); + term_word(p, "\\(lC"); + p->flags |= TERMP_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static void +termp_brq_post(DECL_ARGS) +{ + + if (MDOC_BODY != n->type) + return; + p->flags |= TERMP_NOSPACE; + term_word(p, "\\(rC"); +} + + +/* ARGSUSED */ +static int +termp_bq_pre(DECL_ARGS) +{ + + if (MDOC_BODY != n->type) + return(1); + term_word(p, "\\(lB"); + p->flags |= TERMP_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static void +termp_bq_post(DECL_ARGS) +{ + + if (MDOC_BODY != n->type) + return; + p->flags |= TERMP_NOSPACE; + term_word(p, "\\(rB"); +} + + +/* ARGSUSED */ +static int +termp_pq_pre(DECL_ARGS) +{ + + if (MDOC_BODY != n->type) + return(1); + term_word(p, "\\&("); + p->flags |= TERMP_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static void +termp_pq_post(DECL_ARGS) +{ + + if (MDOC_BODY != n->type) + return; + term_word(p, ")"); +} + + +/* ARGSUSED */ +static int +termp_fo_pre(DECL_ARGS) +{ + const struct mdoc_node *nn; + + if (MDOC_BODY == n->type) { + p->flags |= TERMP_NOSPACE; + term_word(p, "("); + p->flags |= TERMP_NOSPACE; + return(1); + } else if (MDOC_HEAD != n->type) + return(1); + + p->bold++; + for (nn = n->child; nn; nn = nn->next) { + assert(MDOC_TEXT == nn->type); + term_word(p, nn->string); + } + p->bold--; + + return(0); +} + + +/* ARGSUSED */ +static void +termp_fo_post(DECL_ARGS) +{ + + if (MDOC_BODY != n->type) + return; + p->flags |= TERMP_NOSPACE; + term_word(p, ")"); + p->flags |= TERMP_NOSPACE; + term_word(p, ";"); + term_newln(p); +} + + +/* ARGSUSED */ +static int +termp_bf_pre(DECL_ARGS) +{ + const struct mdoc_node *nn; + + if (MDOC_HEAD == n->type) + return(0); + else if (MDOC_BLOCK != n->type) + return(1); + + if (NULL == (nn = n->head->child)) { + if (arg_hasattr(MDOC_Emphasis, n)) + p->under++; + else if (arg_hasattr(MDOC_Symbolic, n)) + p->bold++; + + return(1); + } + + assert(MDOC_TEXT == nn->type); + if (0 == strcmp("Em", nn->string)) + p->under++; + else if (0 == strcmp("Sy", nn->string)) + p->bold++; + + return(1); +} + + +/* ARGSUSED */ +static int +termp_sm_pre(DECL_ARGS) +{ + + assert(n->child && MDOC_TEXT == n->child->type); + if (0 == strcmp("on", n->child->string)) { + p->flags &= ~TERMP_NONOSPACE; + p->flags &= ~TERMP_NOSPACE; + } else + p->flags |= TERMP_NONOSPACE; + + return(0); +} + + +/* ARGSUSED */ +static int +termp_ap_pre(DECL_ARGS) +{ + + p->flags |= TERMP_NOSPACE; + term_word(p, "\\(aq"); + p->flags |= TERMP_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static void +termp____post(DECL_ARGS) +{ + + p->flags |= TERMP_NOSPACE; + switch (n->tok) { + case (MDOC__T): + term_word(p, "\\(rq"); + p->flags |= TERMP_NOSPACE; + break; + default: + break; + } + term_word(p, n->next ? "," : "."); +} + + +/* ARGSUSED */ +static int +termp_lk_pre(DECL_ARGS) +{ + const struct mdoc_node *nn; + + if (NULL == (nn = n->child->next)) { + p->under++; + return(1); + } + + p->under++; + term_word(p, nn->string); + p->flags |= TERMP_NOSPACE; + term_word(p, ":"); + p->under--; + + p->bold++; + for (nn = nn->next; nn; nn = nn->next) + term_word(p, nn->string); + p->bold--; + + return(0); +} + + +/* ARGSUSED */ +static int +termp_under_pre(DECL_ARGS) +{ + + p->under++; + return(1); +} + + +/* ARGSUSED */ +static int +termp__t_pre(DECL_ARGS) +{ + + term_word(p, "\\(lq"); + p->flags |= TERMP_NOSPACE; + return(1); +} diff --git a/usr.bin/mandoc/mdoc_validate.c b/usr.bin/mandoc/mdoc_validate.c new file mode 100644 index 0000000000..ab96455eef --- /dev/null +++ b/usr.bin/mandoc/mdoc_validate.c @@ -0,0 +1,1307 @@ +/* $Id: mdoc_validate.c,v 1.37 2009/10/21 19:13:51 schwarze Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "libmdoc.h" +#include "libmandoc.h" + +/* FIXME: .Bl -diag can't have non-text children in HEAD. */ +/* TODO: ignoring Pp (it's superfluous in some invocations). */ + +#define PRE_ARGS struct mdoc *mdoc, const struct mdoc_node *n +#define POST_ARGS struct mdoc *mdoc + +typedef int (*v_pre)(PRE_ARGS); +typedef int (*v_post)(POST_ARGS); + +struct valids { + v_pre *pre; + v_post *post; +}; + +static int check_parent(PRE_ARGS, int, enum mdoc_type); +static int check_msec(PRE_ARGS, ...); +static int check_sec(PRE_ARGS, ...); +static int check_stdarg(PRE_ARGS); +static int check_text(struct mdoc *, int, int, const char *); +static int check_argv(struct mdoc *, + const struct mdoc_node *, + const struct mdoc_argv *); +static int check_args(struct mdoc *, + const struct mdoc_node *); +static int err_child_lt(struct mdoc *, const char *, int); +static int warn_child_lt(struct mdoc *, const char *, int); +static int err_child_gt(struct mdoc *, const char *, int); +static int warn_child_gt(struct mdoc *, const char *, int); +static int err_child_eq(struct mdoc *, const char *, int); +static int warn_child_eq(struct mdoc *, const char *, int); +static int warn_print(struct mdoc *, int, int); +static int warn_count(struct mdoc *, const char *, + int, const char *, int); +static int err_count(struct mdoc *, const char *, + int, const char *, int); + +static int berr_ge1(POST_ARGS); +static int bwarn_ge1(POST_ARGS); +static int ebool(POST_ARGS); +static int eerr_eq0(POST_ARGS); +static int eerr_eq1(POST_ARGS); +static int eerr_ge1(POST_ARGS); +static int eerr_le2(POST_ARGS); +static int eerr_le1(POST_ARGS); +static int ewarn_ge1(POST_ARGS); +static int herr_eq0(POST_ARGS); +static int herr_ge1(POST_ARGS); +static int hwarn_eq1(POST_ARGS); +static int hwarn_le1(POST_ARGS); + +static int post_an(POST_ARGS); +static int post_at(POST_ARGS); +static int post_bf(POST_ARGS); +static int post_bl(POST_ARGS); +static int post_bl_head(POST_ARGS); +static int post_it(POST_ARGS); +static int post_lb(POST_ARGS); +static int post_nm(POST_ARGS); +static int post_root(POST_ARGS); +static int post_rs(POST_ARGS); +static int post_sh(POST_ARGS); +static int post_sh_body(POST_ARGS); +static int post_sh_head(POST_ARGS); +static int post_st(POST_ARGS); +static int pre_an(PRE_ARGS); +static int pre_bd(PRE_ARGS); +static int pre_bl(PRE_ARGS); +static int pre_cd(PRE_ARGS); +static int pre_dd(PRE_ARGS); +static int pre_display(PRE_ARGS); +static int pre_dt(PRE_ARGS); +static int pre_er(PRE_ARGS); +static int pre_ex(PRE_ARGS); +static int pre_fd(PRE_ARGS); +static int pre_it(PRE_ARGS); +static int pre_lb(PRE_ARGS); +static int pre_os(PRE_ARGS); +static int pre_rv(PRE_ARGS); +static int pre_sh(PRE_ARGS); +static int pre_ss(PRE_ARGS); + +static v_post posts_an[] = { post_an, NULL }; +static v_post posts_at[] = { post_at, NULL }; +static v_post posts_bd[] = { herr_eq0, bwarn_ge1, NULL }; +static v_post posts_bf[] = { hwarn_le1, post_bf, NULL }; +static v_post posts_bl[] = { bwarn_ge1, post_bl, NULL }; +static v_post posts_bool[] = { eerr_eq1, ebool, NULL }; +static v_post posts_fo[] = { hwarn_eq1, bwarn_ge1, NULL }; +static v_post posts_in[] = { eerr_eq1, NULL }; +static v_post posts_it[] = { post_it, NULL }; +static v_post posts_lb[] = { eerr_eq1, post_lb, NULL }; +static v_post posts_nd[] = { berr_ge1, NULL }; +static v_post posts_nm[] = { post_nm, NULL }; +static v_post posts_notext[] = { eerr_eq0, NULL }; +static v_post posts_pf[] = { eerr_eq1, NULL }; +static v_post posts_rs[] = { berr_ge1, herr_eq0, post_rs, NULL }; +static v_post posts_sh[] = { herr_ge1, bwarn_ge1, post_sh, NULL }; +static v_post posts_sp[] = { eerr_le1, NULL }; +static v_post posts_ss[] = { herr_ge1, NULL }; +static v_post posts_st[] = { eerr_eq1, post_st, NULL }; +static v_post posts_text[] = { eerr_ge1, NULL }; +static v_post posts_wline[] = { bwarn_ge1, herr_eq0, NULL }; +static v_post posts_wtext[] = { ewarn_ge1, NULL }; +static v_post posts_xr[] = { eerr_ge1, eerr_le2, NULL }; +static v_pre pres_an[] = { pre_an, NULL }; +static v_pre pres_bd[] = { pre_display, pre_bd, NULL }; +static v_pre pres_bl[] = { pre_bl, NULL }; +static v_pre pres_cd[] = { pre_cd, NULL }; +static v_pre pres_d1[] = { pre_display, NULL }; +static v_pre pres_dd[] = { pre_dd, NULL }; +static v_pre pres_dt[] = { pre_dt, NULL }; +static v_pre pres_er[] = { pre_er, NULL }; +static v_pre pres_ex[] = { pre_ex, NULL }; +static v_pre pres_fd[] = { pre_fd, NULL }; +static v_pre pres_it[] = { pre_it, NULL }; +static v_pre pres_lb[] = { pre_lb, NULL }; +static v_pre pres_os[] = { pre_os, NULL }; +static v_pre pres_rv[] = { pre_rv, NULL }; +static v_pre pres_sh[] = { pre_sh, NULL }; +static v_pre pres_ss[] = { pre_ss, NULL }; + +const struct valids mdoc_valids[MDOC_MAX] = { + { NULL, NULL }, /* Ap */ + { pres_dd, posts_text }, /* Dd */ + { pres_dt, NULL }, /* Dt */ + { pres_os, NULL }, /* Os */ + { pres_sh, posts_sh }, /* Sh */ + { pres_ss, posts_ss }, /* Ss */ + { NULL, posts_notext }, /* Pp */ + { pres_d1, posts_wline }, /* D1 */ + { pres_d1, posts_wline }, /* Dl */ + { pres_bd, posts_bd }, /* Bd */ + { NULL, NULL }, /* Ed */ + { pres_bl, posts_bl }, /* Bl */ + { NULL, NULL }, /* El */ + { pres_it, posts_it }, /* It */ + { NULL, posts_text }, /* Ad */ + { pres_an, posts_an }, /* An */ + { NULL, NULL }, /* Ar */ + { pres_cd, posts_text }, /* Cd */ + { NULL, NULL }, /* Cm */ + { NULL, NULL }, /* Dv */ + { pres_er, posts_text }, /* Er */ + { NULL, NULL }, /* Ev */ + { pres_ex, NULL }, /* Ex */ + { NULL, NULL }, /* Fa */ + { pres_fd, posts_wtext }, /* Fd */ + { NULL, NULL }, /* Fl */ + { NULL, posts_text }, /* Fn */ + { NULL, posts_wtext }, /* Ft */ + { NULL, posts_text }, /* Ic */ + { NULL, posts_in }, /* In */ + { NULL, NULL }, /* Li */ + { NULL, posts_nd }, /* Nd */ + { NULL, posts_nm }, /* Nm */ + { NULL, posts_wline }, /* Op */ + { NULL, NULL }, /* Ot */ + { NULL, NULL }, /* Pa */ + { pres_rv, NULL }, /* Rv */ + { NULL, posts_st }, /* St */ + { NULL, NULL }, /* Va */ + { NULL, posts_text }, /* Vt */ + { NULL, posts_xr }, /* Xr */ + { NULL, posts_text }, /* %A */ + { NULL, posts_text }, /* %B */ /* FIXME: can be used outside Rs/Re. */ + { NULL, posts_text }, /* %D */ + { NULL, posts_text }, /* %I */ + { NULL, posts_text }, /* %J */ + { NULL, posts_text }, /* %N */ + { NULL, posts_text }, /* %O */ + { NULL, posts_text }, /* %P */ + { NULL, posts_text }, /* %R */ + { NULL, posts_text }, /* %T */ /* FIXME: can be used outside Rs/Re. */ + { NULL, posts_text }, /* %V */ + { NULL, NULL }, /* Ac */ + { NULL, NULL }, /* Ao */ + { NULL, posts_wline }, /* Aq */ + { NULL, posts_at }, /* At */ + { NULL, NULL }, /* Bc */ + { NULL, posts_bf }, /* Bf */ + { NULL, NULL }, /* Bo */ + { NULL, posts_wline }, /* Bq */ + { NULL, NULL }, /* Bsx */ + { NULL, NULL }, /* Bx */ + { NULL, posts_bool }, /* Db */ + { NULL, NULL }, /* Dc */ + { NULL, NULL }, /* Do */ + { NULL, posts_wline }, /* Dq */ + { NULL, NULL }, /* Ec */ + { NULL, NULL }, /* Ef */ + { NULL, NULL }, /* Em */ + { NULL, NULL }, /* Eo */ + { NULL, NULL }, /* Fx */ + { NULL, posts_text }, /* Ms */ + { NULL, posts_notext }, /* No */ + { NULL, posts_notext }, /* Ns */ + { NULL, NULL }, /* Nx */ + { NULL, NULL }, /* Ox */ + { NULL, NULL }, /* Pc */ + { NULL, posts_pf }, /* Pf */ + { NULL, NULL }, /* Po */ + { NULL, posts_wline }, /* Pq */ + { NULL, NULL }, /* Qc */ + { NULL, posts_wline }, /* Ql */ + { NULL, NULL }, /* Qo */ + { NULL, posts_wline }, /* Qq */ + { NULL, NULL }, /* Re */ + { NULL, posts_rs }, /* Rs */ + { NULL, NULL }, /* Sc */ + { NULL, NULL }, /* So */ + { NULL, posts_wline }, /* Sq */ + { NULL, posts_bool }, /* Sm */ + { NULL, posts_text }, /* Sx */ + { NULL, posts_text }, /* Sy */ + { NULL, posts_text }, /* Tn */ + { NULL, NULL }, /* Ux */ + { NULL, NULL }, /* Xc */ + { NULL, NULL }, /* Xo */ + { NULL, posts_fo }, /* Fo */ + { NULL, NULL }, /* Fc */ + { NULL, NULL }, /* Oo */ + { NULL, NULL }, /* Oc */ + { NULL, posts_wline }, /* Bk */ + { NULL, NULL }, /* Ek */ + { NULL, posts_notext }, /* Bt */ + { NULL, NULL }, /* Hf */ + { NULL, NULL }, /* Fr */ + { NULL, posts_notext }, /* Ud */ + { pres_lb, posts_lb }, /* Lb */ + { NULL, posts_notext }, /* Lp */ + { NULL, NULL }, /* Lk */ + { NULL, posts_text }, /* Mt */ + { NULL, posts_wline }, /* Brq */ + { NULL, NULL }, /* Bro */ + { NULL, NULL }, /* Brc */ + { NULL, posts_text }, /* %C */ + { NULL, NULL }, /* Es */ + { NULL, NULL }, /* En */ + { NULL, NULL }, /* Dx */ + { NULL, posts_text }, /* %Q */ + { NULL, posts_notext }, /* br */ + { NULL, posts_sp }, /* sp */ +}; + + +int +mdoc_valid_pre(struct mdoc *mdoc, const struct mdoc_node *n) +{ + v_pre *p; + int line, pos; + const char *tp; + + if (MDOC_TEXT == n->type) { + tp = n->string; + line = n->line; + pos = n->pos; + return(check_text(mdoc, line, pos, tp)); + } + + if ( ! check_args(mdoc, n)) + return(0); + if (NULL == mdoc_valids[n->tok].pre) + return(1); + for (p = mdoc_valids[n->tok].pre; *p; p++) + if ( ! (*p)(mdoc, n)) + return(0); + return(1); +} + + +int +mdoc_valid_post(struct mdoc *mdoc) +{ + v_post *p; + + if (MDOC_VALID & mdoc->last->flags) + return(1); + mdoc->last->flags |= MDOC_VALID; + + if (MDOC_TEXT == mdoc->last->type) + return(1); + if (MDOC_ROOT == mdoc->last->type) + return(post_root(mdoc)); + + if (NULL == mdoc_valids[mdoc->last->tok].post) + return(1); + for (p = mdoc_valids[mdoc->last->tok].post; *p; p++) + if ( ! (*p)(mdoc)) + return(0); + + return(1); +} + + +static int +warn_print(struct mdoc *m, int ln, int pos) +{ + + if (MDOC_IGN_CHARS & m->pflags) + return(mdoc_pwarn(m, ln, pos, EPRINT)); + return(mdoc_perr(m, ln, pos, EPRINT)); +} + + +static inline int +warn_count(struct mdoc *m, const char *k, + int want, const char *v, int has) +{ + + return(mdoc_vwarn(m, m->last->line, m->last->pos, + "suggests %s %s %d (has %d)", v, k, want, has)); +} + + +static inline int +err_count(struct mdoc *m, const char *k, + int want, const char *v, int has) +{ + + return(mdoc_verr(m, m->last->line, m->last->pos, + "requires %s %s %d (has %d)", v, k, want, has)); +} + + +/* + * Build these up with macros because they're basically the same check + * for different inequalities. Yes, this could be done with functions, + * but this is reasonable for now. + */ + +#define CHECK_CHILD_DEFN(lvl, name, ineq) \ +static int \ +lvl##_child_##name(struct mdoc *mdoc, const char *p, int sz) \ +{ \ + if (mdoc->last->nchild ineq sz) \ + return(1); \ + return(lvl##_count(mdoc, #ineq, sz, p, mdoc->last->nchild)); \ +} + +#define CHECK_BODY_DEFN(name, lvl, func, num) \ +static int \ +b##lvl##_##name(POST_ARGS) \ +{ \ + if (MDOC_BODY != mdoc->last->type) \ + return(1); \ + return(func(mdoc, "multi-line arguments", (num))); \ +} + +#define CHECK_ELEM_DEFN(name, lvl, func, num) \ +static int \ +e##lvl##_##name(POST_ARGS) \ +{ \ + assert(MDOC_ELEM == mdoc->last->type); \ + return(func(mdoc, "line arguments", (num))); \ +} + +#define CHECK_HEAD_DEFN(name, lvl, func, num) \ +static int \ +h##lvl##_##name(POST_ARGS) \ +{ \ + if (MDOC_HEAD != mdoc->last->type) \ + return(1); \ + return(func(mdoc, "line arguments", (num))); \ +} + + +CHECK_CHILD_DEFN(warn, gt, >) /* warn_child_gt() */ +CHECK_CHILD_DEFN(err, gt, >) /* err_child_gt() */ +CHECK_CHILD_DEFN(warn, eq, ==) /* warn_child_eq() */ +CHECK_CHILD_DEFN(err, eq, ==) /* err_child_eq() */ +CHECK_CHILD_DEFN(err, lt, <) /* err_child_lt() */ +CHECK_CHILD_DEFN(warn, lt, <) /* warn_child_lt() */ +CHECK_BODY_DEFN(ge1, warn, warn_child_gt, 0) /* bwarn_ge1() */ +CHECK_BODY_DEFN(ge1, err, err_child_gt, 0) /* berr_ge1() */ +CHECK_ELEM_DEFN(ge1, warn, warn_child_gt, 0) /* ewarn_gt1() */ +CHECK_ELEM_DEFN(eq1, err, err_child_eq, 1) /* eerr_eq1() */ +CHECK_ELEM_DEFN(le2, err, err_child_lt, 3) /* eerr_le2() */ +CHECK_ELEM_DEFN(le1, err, err_child_lt, 2) /* eerr_le1() */ +CHECK_ELEM_DEFN(eq0, err, err_child_eq, 0) /* eerr_eq0() */ +CHECK_ELEM_DEFN(ge1, err, err_child_gt, 0) /* eerr_ge1() */ +CHECK_HEAD_DEFN(eq0, err, err_child_eq, 0) /* herr_eq0() */ +CHECK_HEAD_DEFN(le1, warn, warn_child_lt, 2) /* hwarn_le1() */ +CHECK_HEAD_DEFN(ge1, err, err_child_gt, 0) /* herr_ge1() */ +CHECK_HEAD_DEFN(eq1, warn, warn_child_eq, 1) /* hwarn_eq1() */ + + +static int +check_stdarg(PRE_ARGS) +{ + + if (n->args && 1 == n->args->argc) + if (MDOC_Std == n->args->argv[0].arg) + return(1); + return(mdoc_nwarn(mdoc, n, EARGVAL)); +} + + +static int +check_sec(PRE_ARGS, ...) +{ + enum mdoc_sec sec; + va_list ap; + + va_start(ap, n); + + for (;;) { + /* LINTED */ + sec = (enum mdoc_sec)va_arg(ap, int); + if (SEC_CUSTOM == sec) + break; + if (sec != mdoc->lastsec) + continue; + va_end(ap); + return(1); + } + + va_end(ap); + return(mdoc_nwarn(mdoc, n, EBADSEC)); +} + + +static int +check_msec(PRE_ARGS, ...) +{ + va_list ap; + int msec; + + va_start(ap, n); + for (;;) { + /* LINTED */ + if (0 == (msec = va_arg(ap, int))) + break; + if (msec != mdoc->meta.msec) + continue; + va_end(ap); + return(1); + } + + va_end(ap); + return(mdoc_nwarn(mdoc, n, EBADMSEC)); +} + + +static int +check_args(struct mdoc *m, const struct mdoc_node *n) +{ + int i; + + if (NULL == n->args) + return(1); + + assert(n->args->argc); + for (i = 0; i < (int)n->args->argc; i++) + if ( ! check_argv(m, n, &n->args->argv[i])) + return(0); + + return(1); +} + + +static int +check_argv(struct mdoc *m, const struct mdoc_node *n, + const struct mdoc_argv *v) +{ + int i; + + for (i = 0; i < (int)v->sz; i++) + if ( ! check_text(m, v->line, v->pos, v->value[i])) + return(0); + + if (MDOC_Std == v->arg) { + /* `Nm' name must be set. */ + if (v->sz || m->meta.name) + return(1); + return(mdoc_nerr(m, n, ENAME)); + } + + return(1); +} + + +static int +check_text(struct mdoc *mdoc, int line, int pos, const char *p) +{ + int c; + + for ( ; *p; p++, pos++) { + if ('\t' == *p) { + if ( ! (MDOC_LITERAL & mdoc->flags)) + if ( ! warn_print(mdoc, line, pos)) + return(0); + } else if ( ! isprint((u_char)*p)) + if ( ! warn_print(mdoc, line, pos)) + return(0); + + if ('\\' != *p) + continue; + + c = mandoc_special(p); + if (c) { + p += c - 1; + pos += c - 1; + continue; + } + if ( ! (MDOC_IGN_ESCAPE & mdoc->pflags)) + return(mdoc_perr(mdoc, line, pos, EESCAPE)); + if ( ! mdoc_pwarn(mdoc, line, pos, EESCAPE)) + return(0); + } + + return(1); +} + + + + +static int +check_parent(PRE_ARGS, int tok, enum mdoc_type t) +{ + + assert(n->parent); + if ((MDOC_ROOT == t || tok == n->parent->tok) && + (t == n->parent->type)) + return(1); + + return(mdoc_verr(mdoc, n->line, n->pos, "require parent %s", + MDOC_ROOT == t ? "" : mdoc_macronames[tok])); +} + + + +static int +pre_display(PRE_ARGS) +{ + struct mdoc_node *node; + + /* Display elements (`Bd', `D1'...) cannot be nested. */ + + if (MDOC_BLOCK != n->type) + return(1); + + /* LINTED */ + for (node = mdoc->last->parent; node; node = node->parent) + if (MDOC_BLOCK == node->type) + if (MDOC_Bd == node->tok) + break; + if (NULL == node) + return(1); + + return(mdoc_nerr(mdoc, n, ENESTDISP)); +} + + +static int +pre_bl(PRE_ARGS) +{ + int pos, type, width, offset; + + if (MDOC_BLOCK != n->type) + return(1); + if (NULL == n->args) + return(mdoc_nerr(mdoc, n, ELISTTYPE)); + + /* Make sure that only one type of list is specified. */ + + type = offset = width = -1; + + /* LINTED */ + for (pos = 0; pos < (int)n->args->argc; pos++) + switch (n->args->argv[pos].arg) { + case (MDOC_Bullet): + /* FALLTHROUGH */ + case (MDOC_Dash): + /* FALLTHROUGH */ + case (MDOC_Enum): + /* FALLTHROUGH */ + case (MDOC_Hyphen): + /* FALLTHROUGH */ + case (MDOC_Item): + /* FALLTHROUGH */ + case (MDOC_Tag): + /* FALLTHROUGH */ + case (MDOC_Diag): + /* FALLTHROUGH */ + case (MDOC_Hang): + /* FALLTHROUGH */ + case (MDOC_Ohang): + /* FALLTHROUGH */ + case (MDOC_Inset): + /* FALLTHROUGH */ + case (MDOC_Column): + if (type >= 0) + return(mdoc_nerr(mdoc, n, EMULTILIST)); + type = n->args->argv[pos].arg; + break; + case (MDOC_Compact): + if (type < 0 && ! mdoc_nwarn(mdoc, n, ENOTYPE)) + return(0); + break; + case (MDOC_Width): + if (width >= 0) + return(mdoc_nerr(mdoc, n, EARGREP)); + if (type < 0 && ! mdoc_nwarn(mdoc, n, ENOTYPE)) + return(0); + width = n->args->argv[pos].arg; + break; + case (MDOC_Offset): + if (offset >= 0) + return(mdoc_nerr(mdoc, n, EARGREP)); + if (type < 0 && ! mdoc_nwarn(mdoc, n, ENOTYPE)) + return(0); + offset = n->args->argv[pos].arg; + break; + default: + break; + } + + if (type < 0) + return(mdoc_nerr(mdoc, n, ELISTTYPE)); + + /* + * Validate the width field. Some list types don't need width + * types and should be warned about them. Others should have it + * and must also be warned. + */ + + switch (type) { + case (MDOC_Tag): + if (width < 0 && ! mdoc_nwarn(mdoc, n, EMISSWIDTH)) + return(0); + break; + case (MDOC_Column): + /* FALLTHROUGH */ + case (MDOC_Diag): + /* FALLTHROUGH */ + case (MDOC_Inset): + /* FALLTHROUGH */ + case (MDOC_Item): + if (width >= 0 && ! mdoc_nwarn(mdoc, n, ENOWIDTH)) + return(0); + break; + default: + break; + } + + return(1); +} + + +static int +pre_bd(PRE_ARGS) +{ + int i, type, err; + + if (MDOC_BLOCK != n->type) + return(1); + if (NULL == n->args) + return(mdoc_nerr(mdoc, n, EDISPTYPE)); + + /* Make sure that only one type of display is specified. */ + + /* LINTED */ + for (i = 0, err = type = 0; ! err && + i < (int)n->args->argc; i++) + switch (n->args->argv[i].arg) { + case (MDOC_Ragged): + /* FALLTHROUGH */ + case (MDOC_Unfilled): + /* FALLTHROUGH */ + case (MDOC_Filled): + /* FALLTHROUGH */ + case (MDOC_Literal): + if (0 == type++) + break; + return(mdoc_nerr(mdoc, n, EMULTIDISP)); + default: + break; + } + + if (type) + return(1); + return(mdoc_nerr(mdoc, n, EDISPTYPE)); +} + + +static int +pre_ss(PRE_ARGS) +{ + + if (MDOC_BLOCK != n->type) + return(1); + return(check_parent(mdoc, n, MDOC_Sh, MDOC_BODY)); +} + + +static int +pre_sh(PRE_ARGS) +{ + + if (MDOC_BLOCK != n->type) + return(1); + return(check_parent(mdoc, n, -1, MDOC_ROOT)); +} + + +static int +pre_it(PRE_ARGS) +{ + + if (MDOC_BLOCK != n->type) + return(1); + return(check_parent(mdoc, n, MDOC_Bl, MDOC_BODY)); +} + + +static int +pre_an(PRE_ARGS) +{ + + if (NULL == n->args || 1 == n->args->argc) + return(1); + return(mdoc_verr(mdoc, n->line, n->pos, + "only one argument allowed")); +} + + +static int +pre_lb(PRE_ARGS) +{ + + return(check_sec(mdoc, n, SEC_LIBRARY, SEC_CUSTOM)); +} + + +static int +pre_rv(PRE_ARGS) +{ + + if ( ! check_msec(mdoc, n, 2, 3, 0)) + return(0); + return(check_stdarg(mdoc, n)); +} + + +static int +pre_ex(PRE_ARGS) +{ + + if ( ! check_msec(mdoc, n, 1, 6, 8, 0)) + return(0); + return(check_stdarg(mdoc, n)); +} + + +static int +pre_er(PRE_ARGS) +{ + + return(check_msec(mdoc, n, 2, 3, 9, 0)); +} + + +static int +pre_cd(PRE_ARGS) +{ + + return(check_msec(mdoc, n, 4, 0)); +} + + +static int +pre_dt(PRE_ARGS) +{ + + if (0 == mdoc->meta.date || mdoc->meta.os) + if ( ! mdoc_nwarn(mdoc, n, EPROLOOO)) + return(0); + if (mdoc->meta.title) + if ( ! mdoc_nwarn(mdoc, n, EPROLREP)) + return(0); + return(1); +} + + +static int +pre_os(PRE_ARGS) +{ + + if (NULL == mdoc->meta.title || 0 == mdoc->meta.date) + if ( ! mdoc_nwarn(mdoc, n, EPROLOOO)) + return(0); + if (mdoc->meta.os) + if ( ! mdoc_nwarn(mdoc, n, EPROLREP)) + return(0); + return(1); +} + + +static int +pre_dd(PRE_ARGS) +{ + + if (mdoc->meta.title || mdoc->meta.os) + if ( ! mdoc_nwarn(mdoc, n, EPROLOOO)) + return(0); + if (mdoc->meta.date) + if ( ! mdoc_nwarn(mdoc, n, EPROLREP)) + return(0); + return(1); +} + + +static int +post_bf(POST_ARGS) +{ + char *p; + struct mdoc_node *head; + + if (MDOC_BLOCK != mdoc->last->type) + return(1); + + head = mdoc->last->head; + + if (mdoc->last->args && head->child) + return(mdoc_nerr(mdoc, mdoc->last, ELINE)); + else if (mdoc->last->args) + return(1); + + if (NULL == head->child || MDOC_TEXT != head->child->type) + return(mdoc_nerr(mdoc, mdoc->last, ELINE)); + + p = head->child->string; + + if (0 == strcmp(p, "Em")) + return(1); + else if (0 == strcmp(p, "Li")) + return(1); + else if (0 == strcmp(p, "Sy")) + return(1); + + return(mdoc_nerr(mdoc, head, EFONT)); +} + + +static int +post_lb(POST_ARGS) +{ + + if (mdoc_a2lib(mdoc->last->child->string)) + return(1); + return(mdoc_nwarn(mdoc, mdoc->last, ELIB)); +} + + +static int +post_nm(POST_ARGS) +{ + + if (mdoc->last->child) + return(1); + if (mdoc->meta.name) + return(1); + return(mdoc_nerr(mdoc, mdoc->last, ENAME)); +} + + +static int +post_at(POST_ARGS) +{ + + if (NULL == mdoc->last->child) + return(1); + if (MDOC_TEXT != mdoc->last->child->type) + return(mdoc_nerr(mdoc, mdoc->last, EATT)); + if (mdoc_a2att(mdoc->last->child->string)) + return(1); + return(mdoc_nerr(mdoc, mdoc->last, EATT)); +} + + +static int +post_an(POST_ARGS) +{ + + if (mdoc->last->args) { + if (NULL == mdoc->last->child) + return(1); + return(mdoc_nerr(mdoc, mdoc->last, ENOLINE)); + } + + if (mdoc->last->child) + return(1); + return(mdoc_nerr(mdoc, mdoc->last, ELINE)); +} + + +static int +post_it(POST_ARGS) +{ + int type, i, cols; + struct mdoc_node *n, *c; + + if (MDOC_BLOCK != mdoc->last->type) + return(1); + + n = mdoc->last->parent->parent; + if (NULL == n->args) + return(mdoc_nerr(mdoc, mdoc->last, ELISTTYPE)); + + /* Some types require block-head, some not. */ + + /* LINTED */ + for (cols = type = -1, i = 0; -1 == type && + i < (int)n->args->argc; i++) + switch (n->args->argv[i].arg) { + case (MDOC_Tag): + /* FALLTHROUGH */ + case (MDOC_Diag): + /* FALLTHROUGH */ + case (MDOC_Hang): + /* FALLTHROUGH */ + case (MDOC_Ohang): + /* FALLTHROUGH */ + case (MDOC_Inset): + /* FALLTHROUGH */ + case (MDOC_Bullet): + /* FALLTHROUGH */ + case (MDOC_Dash): + /* FALLTHROUGH */ + case (MDOC_Enum): + /* FALLTHROUGH */ + case (MDOC_Hyphen): + /* FALLTHROUGH */ + case (MDOC_Item): + type = n->args->argv[i].arg; + break; + case (MDOC_Column): + type = n->args->argv[i].arg; + cols = (int)n->args->argv[i].sz; + break; + default: + break; + } + + if (-1 == type) + return(mdoc_nerr(mdoc, mdoc->last, ELISTTYPE)); + + switch (type) { + case (MDOC_Tag): + if (NULL == mdoc->last->head->child) + if ( ! mdoc_nwarn(mdoc, mdoc->last, ELINE)) + return(0); + break; + case (MDOC_Hang): + /* FALLTHROUGH */ + case (MDOC_Ohang): + /* FALLTHROUGH */ + case (MDOC_Inset): + /* FALLTHROUGH */ + case (MDOC_Diag): + if (NULL == mdoc->last->head->child) + if ( ! mdoc_nwarn(mdoc, mdoc->last, ELINE)) + return(0); + if (NULL == mdoc->last->body->child) + if ( ! mdoc_nwarn(mdoc, mdoc->last, EMULTILINE)) + return(0); + break; + case (MDOC_Bullet): + /* FALLTHROUGH */ + case (MDOC_Dash): + /* FALLTHROUGH */ + case (MDOC_Enum): + /* FALLTHROUGH */ + case (MDOC_Hyphen): + /* FALLTHROUGH */ + case (MDOC_Item): + if (mdoc->last->head->child) + if ( ! mdoc_nwarn(mdoc, mdoc->last, ENOLINE)) + return(0); + if (NULL == mdoc->last->body->child) + if ( ! mdoc_nwarn(mdoc, mdoc->last, EMULTILINE)) + return(0); + break; + case (MDOC_Column): + if (NULL == mdoc->last->head->child) + if ( ! mdoc_nwarn(mdoc, mdoc->last, ELINE)) + return(0); + if (mdoc->last->body->child) + if ( ! mdoc_nwarn(mdoc, mdoc->last, ENOMULTILINE)) + return(0); + c = mdoc->last->child; + for (i = 0; c && MDOC_HEAD == c->type; c = c->next) + i++; + + if (i < cols || i == (cols + 1)) { + if ( ! mdoc_vwarn(mdoc, mdoc->last->line, + mdoc->last->pos, "column " + "mismatch: have %d, want %d", + i, cols)) + return(0); + break; + } else if (i == cols) + break; + + return(mdoc_verr(mdoc, mdoc->last->line, + mdoc->last->pos, "column mismatch: " + "have %d, want %d", i, cols)); + default: + break; + } + + return(1); +} + + +static int +post_bl_head(POST_ARGS) +{ + int i; + const struct mdoc_node *n; + + n = mdoc->last->parent; + assert(n->args); + + for (i = 0; i < (int)n->args->argc; i++) + if (n->args->argv[i].arg == MDOC_Column) + break; + + if (i == (int)n->args->argc) + return(1); + + if (n->args->argv[i].sz && mdoc->last->child) + return(mdoc_nerr(mdoc, n, ECOLMIS)); + + return(1); +} + + +static int +post_bl(POST_ARGS) +{ + struct mdoc_node *n; + + if (MDOC_HEAD == mdoc->last->type) + return(post_bl_head(mdoc)); + if (MDOC_BODY != mdoc->last->type) + return(1); + if (NULL == mdoc->last->child) + return(1); + + /* LINTED */ + for (n = mdoc->last->child; n; n = n->next) { + if (MDOC_BLOCK == n->type) + if (MDOC_It == n->tok) + continue; + return(mdoc_nerr(mdoc, n, EBADCHILD)); + } + + return(1); +} + + +static int +ebool(struct mdoc *mdoc) +{ + struct mdoc_node *n; + + /* LINTED */ + for (n = mdoc->last->child; n; n = n->next) { + if (MDOC_TEXT != n->type) + break; + if (0 == strcmp(n->string, "on")) + continue; + if (0 == strcmp(n->string, "off")) + continue; + break; + } + + if (NULL == n) + return(1); + return(mdoc_nerr(mdoc, n, EBOOL)); +} + + +static int +post_root(POST_ARGS) +{ + + if (NULL == mdoc->first->child) + return(mdoc_nerr(mdoc, mdoc->first, ENODAT)); + if ( ! (MDOC_PBODY & mdoc->flags)) + return(mdoc_nerr(mdoc, mdoc->first, ENOPROLOGUE)); + + if (MDOC_BLOCK != mdoc->first->child->type) + return(mdoc_nerr(mdoc, mdoc->first, ENODAT)); + if (MDOC_Sh != mdoc->first->child->tok) + return(mdoc_nerr(mdoc, mdoc->first, ENODAT)); + + return(1); +} + + +static int +post_st(POST_ARGS) +{ + + if (mdoc_a2st(mdoc->last->child->string)) + return(1); + return(mdoc_nerr(mdoc, mdoc->last, EBADSTAND)); +} + + +static int +post_rs(POST_ARGS) +{ + struct mdoc_node *nn; + + if (MDOC_BODY != mdoc->last->type) + return(1); + + for (nn = mdoc->last->child; nn; nn = nn->next) + switch (nn->tok) { + case(MDOC__Q): + /* FALLTHROUGH */ + case(MDOC__C): + /* FALLTHROUGH */ + case(MDOC__A): + /* FALLTHROUGH */ + case(MDOC__B): + /* FALLTHROUGH */ + case(MDOC__D): + /* FALLTHROUGH */ + case(MDOC__I): + /* FALLTHROUGH */ + case(MDOC__J): + /* FALLTHROUGH */ + case(MDOC__N): + /* FALLTHROUGH */ + case(MDOC__O): + /* FALLTHROUGH */ + case(MDOC__P): + /* FALLTHROUGH */ + case(MDOC__R): + /* FALLTHROUGH */ + case(MDOC__T): + /* FALLTHROUGH */ + case(MDOC__V): + break; + default: + return(mdoc_nerr(mdoc, nn, EBADCHILD)); + } + + return(1); +} + + +static int +post_sh(POST_ARGS) +{ + + if (MDOC_HEAD == mdoc->last->type) + return(post_sh_head(mdoc)); + if (MDOC_BODY == mdoc->last->type) + return(post_sh_body(mdoc)); + + return(1); +} + + +static int +post_sh_body(POST_ARGS) +{ + struct mdoc_node *n; + + if (SEC_NAME != mdoc->lastsec) + return(1); + + /* + * Warn if the NAME section doesn't contain the `Nm' and `Nd' + * macros (can have multiple `Nm' and one `Nd'). Note that the + * children of the BODY declaration can also be "text". + */ + + if (NULL == (n = mdoc->last->child)) + return(mdoc_nwarn(mdoc, mdoc->last, ENAMESECINC)); + + for ( ; n && n->next; n = n->next) { + if (MDOC_ELEM == n->type && MDOC_Nm == n->tok) + continue; + if (MDOC_TEXT == n->type) + continue; + if ( ! mdoc_nwarn(mdoc, mdoc->last, ENAMESECINC)) + return(0); + } + + assert(n); + if (MDOC_BLOCK == n->type && MDOC_Nd == n->tok) + return(1); + return(mdoc_nwarn(mdoc, mdoc->last, ENAMESECINC)); +} + + +static int +post_sh_head(POST_ARGS) +{ + char buf[64]; + enum mdoc_sec sec; + const struct mdoc_node *n; + + /* + * Process a new section. Sections are either "named" or + * "custom"; custom sections are user-defined, while named ones + * usually follow a conventional order and may only appear in + * certain manual sections. + */ + + buf[0] = 0; + + for (n = mdoc->last->child; n; n = n->next) { + /* XXX - copied from compact(). */ + assert(MDOC_TEXT == n->type); + + if (strlcat(buf, n->string, 64) >= 64) + return(mdoc_nerr(mdoc, n, ETOOLONG)); + if (NULL == n->next) + continue; + if (strlcat(buf, " ", 64) >= 64) + return(mdoc_nerr(mdoc, n, ETOOLONG)); + } + + sec = mdoc_atosec(buf); + + /* + * Check: NAME should always be first, CUSTOM has no roles, + * non-CUSTOM has a conventional order to be followed. + */ + + if (SEC_NAME != sec && SEC_NONE == mdoc->lastnamed) + return(mdoc_nerr(mdoc, mdoc->last, ESECNAME)); + if (SEC_CUSTOM == sec) + return(1); + if (sec == mdoc->lastnamed) + if ( ! mdoc_nwarn(mdoc, mdoc->last, ESECREP)) + return(0); + if (sec < mdoc->lastnamed) + if ( ! mdoc_nwarn(mdoc, mdoc->last, ESECOOO)) + return(0); + + /* + * Check particular section/manual conventions. LIBRARY can + * only occur in msec 2, 3 (TODO: are there more of these?). + */ + + switch (sec) { + case (SEC_LIBRARY): + switch (mdoc->meta.msec) { + case (2): + /* FALLTHROUGH */ + case (3): + break; + default: + return(mdoc_nwarn(mdoc, mdoc->last, EWRONGMSEC)); + } + break; + default: + break; + } + + return(1); +} + + +static int +pre_fd(PRE_ARGS) +{ + + return(check_sec(mdoc, n, SEC_SYNOPSIS, SEC_CUSTOM)); +} diff --git a/usr.bin/mandoc/msec.c b/usr.bin/mandoc/msec.c new file mode 100644 index 0000000000..569fa056f9 --- /dev/null +++ b/usr.bin/mandoc/msec.c @@ -0,0 +1,32 @@ +/* $Id: msec.c,v 1.2 2009/06/14 23:00:57 schwarze Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include + +#include "libmdoc.h" + +#define LINE(x, y) \ + if (0 == strcmp(p, x)) return(y); + +const char * +mdoc_a2msec(const char *p) +{ + +#include "msec.in" + + return(NULL); +} diff --git a/usr.bin/mandoc/msec.in b/usr.bin/mandoc/msec.in new file mode 100644 index 0000000000..9112c44abd --- /dev/null +++ b/usr.bin/mandoc/msec.in @@ -0,0 +1,40 @@ +/* $Id: msec.in,v 1.3 2009/06/18 21:29:32 schwarze Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * These are all possible manual-section macros and what they correspond + * to when rendered as the volume title. + * + * Be sure to escape strings. + */ + +LINE("1", "OpenBSD Reference Manual") +LINE("2", "OpenBSD Programmer\'s Manual") +LINE("3", "OpenBSD Programmer\'s Manual") +LINE("3p", "Perl Programmers Reference Guide") +LINE("4", "OpenBSD Programmer\'s Manual") +LINE("5", "OpenBSD Programmer\'s Manual") +LINE("6", "OpenBSD Reference Manual") +LINE("7", "OpenBSD Reference Manual") +LINE("8", "OpenBSD System Manager's Manual") +LINE("9", "OpenBSD Kernel Manual") +LINE("X11", "X11 Developer\'s Manual") +LINE("X11R6", "X11 Developer\'s Manual") +LINE("unass", "Unassociated") +LINE("local", "Local") +LINE("draft", "Draft") +LINE("paper", "Paper") diff --git a/usr.bin/mandoc/out.c b/usr.bin/mandoc/out.c new file mode 100644 index 0000000000..228dd9c57f --- /dev/null +++ b/usr.bin/mandoc/out.c @@ -0,0 +1,119 @@ +/* $Id: out.c,v 1.1 2009/10/21 19:13:51 schwarze Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include + +#include +#include +#include + +#include "out.h" + + +/* + * Convert a `scaling unit' to a consistent form, or fail. Scaling + * units are documented in groff.7, mdoc.7, man.7. + */ +int +a2roffsu(const char *src, struct roffsu *dst, enum roffscale def) +{ + char buf[BUFSIZ], hasd; + int i; + enum roffscale unit; + + if ('\0' == *src) + return(0); + + i = hasd = 0; + + switch (*src) { + case ('+'): + src++; + break; + case ('-'): + buf[i++] = *src++; + break; + default: + break; + } + + if ('\0' == *src) + return(0); + + while (i < BUFSIZ) { + if ( ! isdigit((u_char)*src)) { + if ('.' != *src) + break; + else if (hasd) + break; + else + hasd = 1; + } + buf[i++] = *src++; + } + + if (BUFSIZ == i || (*src && *(src + 1))) + return(0); + + buf[i] = '\0'; + + switch (*src) { + case ('c'): + unit = SCALE_CM; + break; + case ('i'): + unit = SCALE_IN; + break; + case ('P'): + unit = SCALE_PC; + break; + case ('p'): + unit = SCALE_PT; + break; + case ('f'): + unit = SCALE_FS; + break; + case ('v'): + unit = SCALE_VS; + break; + case ('m'): + unit = SCALE_EM; + break; + case ('\0'): + if (SCALE_MAX == def) + return(0); + unit = SCALE_BU; + break; + case ('u'): + unit = SCALE_BU; + break; + case ('M'): + unit = SCALE_MM; + break; + case ('n'): + unit = SCALE_EN; + break; + default: + return(0); + } + + if ((dst->scale = atof(buf)) < 0) + dst->scale = 0; + dst->unit = unit; + dst->pt = hasd; + + return(1); +} diff --git a/usr.bin/mandoc/out.h b/usr.bin/mandoc/out.h new file mode 100644 index 0000000000..9ce9ecff7f --- /dev/null +++ b/usr.bin/mandoc/out.h @@ -0,0 +1,58 @@ +/* $Id: out.h,v 1.1 2009/10/21 19:13:51 schwarze Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef OUT_H +#define OUT_H + +__BEGIN_DECLS + +enum roffscale { + SCALE_CM, + SCALE_IN, + SCALE_PC, + SCALE_PT, + SCALE_EM, + SCALE_MM, + SCALE_EN, + SCALE_BU, + SCALE_VS, + SCALE_FS, + SCALE_MAX +}; + +struct roffsu { + enum roffscale unit; + double scale; + int pt; +}; + +#define SCALE_INVERT(p) \ + do { (p)->scale = -(p)->scale; } while (/*CONSTCOND*/0) +#define SCALE_VS_INIT(p, v) \ + do { (p)->unit = SCALE_VS; \ + (p)->scale = (v); \ + (p)->pt = 0; } while (/*CONSTCOND*/0) +#define SCALE_HS_INIT(p, v) \ + do { (p)->unit = SCALE_BU; \ + (p)->scale = (v); \ + (p)->pt = 0; } while (/*CONSTCOND*/0) + +int a2roffsu(const char *, + struct roffsu *, enum roffscale); + +__END_DECLS + +#endif /*!HTML_H*/ diff --git a/usr.bin/mandoc/st.c b/usr.bin/mandoc/st.c new file mode 100644 index 0000000000..bac77eefbc --- /dev/null +++ b/usr.bin/mandoc/st.c @@ -0,0 +1,32 @@ +/* $Id: st.c,v 1.2 2009/06/14 23:00:57 schwarze Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include + +#include "libmdoc.h" + +#define LINE(x, y) \ + if (0 == strcmp(p, x)) return(y); + +const char * +mdoc_a2st(const char *p) +{ + +#include "st.in" + + return(NULL); +} diff --git a/usr.bin/mandoc/st.in b/usr.bin/mandoc/st.in new file mode 100644 index 0000000000..dc0d5f5e05 --- /dev/null +++ b/usr.bin/mandoc/st.in @@ -0,0 +1,68 @@ +/* $Id: st.in,v 1.5 2009/10/19 20:39:58 schwarze Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This file defines the .St macro arguments. If you add a new + * standard, make sure that the left-and side corresponds to the .St + * argument (like .St -p1003.1) and the right-hand side corresponds to + * the formatted output string. + * + * Be sure to escape strings. + */ + +LINE("-p1003.1-88", "IEEE Std 1003.1-1988 (\\(lqPOSIX\\(rq)") +LINE("-p1003.1-90", "IEEE Std 1003.1-1990 (\\(lqPOSIX\\(rq)") +LINE("-p1003.1-96", "ISO/IEC 9945-1:1996 (\\(lqPOSIX\\(rq)") +LINE("-p1003.1-2001", "IEEE Std 1003.1-2001 (\\(lqPOSIX\\(rq)") +LINE("-p1003.1-2004", "IEEE Std 1003.1-2004 (\\(lqPOSIX\\(rq)") +LINE("-p1003.1-2008", "IEEE Std 1003.1-2008 (\\(lqPOSIX\\(rq)") +LINE("-p1003.1", "IEEE Std 1003.1 (\\(lqPOSIX\\(rq)") +LINE("-p1003.1b", "IEEE Std 1003.1b (\\(lqPOSIX\\(rq)") +LINE("-p1003.1b-93", "IEEE Std 1003.1b-1993 (\\(lqPOSIX\\(rq)") +LINE("-p1003.1c-95", "IEEE Std 1003.1c-1995 (\\(lqPOSIX\\(rq)") +LINE("-p1003.1g-2000", "IEEE Std 1003.1g-2000 (\\(lqPOSIX\\(rq)") +LINE("-p1003.1i-95", "IEEE Std 1003.1i-1995 (\\(lqPOSIX.1\\(rq)") +LINE("-p1003.2-92", "IEEE Std 1003.2-1992 (\\(lqPOSIX.2\\(rq)") +LINE("-p1387.2-95", "IEEE Std 1387.2-1995 (\\(lqPOSIX.7.2\\(rq)") +LINE("-p1003.2", "IEEE Std 1003.2 (\\(lqPOSIX.2\\(rq)") +LINE("-p1387.2", "IEEE Std 1387.2 (\\(lqPOSIX.7.2\\(rq)") +LINE("-isoC", "ISO/IEC 9899:1990 (\\(lqISO C90\\(rq)") +LINE("-isoC-90", "ISO/IEC 9899:1990 (\\(lqISO C90\\(rq)") +LINE("-isoC-amd1", "ISO/IEC 9899/AMD1:1995 (\\(lqISO C90\\(rq)") +LINE("-isoC-tcor1", "ISO/IEC 9899/TCOR1:1994 (\\(lqISO C90\\(rq)") +LINE("-isoC-tcor2", "ISO/IEC 9899/TCOR2:1995 (\\(lqISO C90\\(rq)") +LINE("-isoC-99", "ISO/IEC 9899:1999 (\\(lqISO C99\\(rq)") +LINE("-ansiC", "ANSI X3.159-1989 (\\(lqANSI C\\(rq)") +LINE("-ansiC-89", "ANSI X3.159-1989 (\\(lqANSI C\\(rq)") +LINE("-ansiC-99", "ANSI/ISO/IEC 9899-1999 (\\(lqANSI C99\\(rq)") +LINE("-ieee754", "IEEE Std 754-1985") +LINE("-iso8802-3", "ISO 8802-3: 1989") +LINE("-ieee1275-94", "IEEE Std 1275-1994 (\\(lqOpen Firmware\\(rq)") +LINE("-xpg3", "X/Open Portability Guide Issue 3 (\\(lqXPG3\\(rq)") +LINE("-xpg4", "X/Open Portability Guide Issue 4 (\\(lqXPG4\\(rq)") +LINE("-xpg4.2", "X/Open Portability Guide Issue 4.2 (\\(lqXPG4.2\\(rq)") +LINE("-xpg4.3", "X/Open Portability Guide Issue 4.3 (\\(lqXPG4.3\\(rq)") +LINE("-xbd5", "X/Open System Interface Definitions Issue 5 (\\(lqXBD5\\(rq)") +LINE("-xcu5", "X/Open Commands and Utilities Issue 5 (\\(lqXCU5\\(rq)") +LINE("-xsh5", "X/Open System Interfaces and Headers Issue 5 (\\(lqXSH5\\(rq)") +LINE("-xns5", "X/Open Networking Services Issue 5 (\\(lqXNS5\\(rq)") +LINE("-xns5.2", "X/Open Networking Services Issue 5.2 (\\(lqXNS5.2\\(rq)") +LINE("-xns5.2d2.0", "X/Open Networking Services Issue 5.2 Draft 2.0 (\\(lqXNS5.2D2.0\\(rq)") +LINE("-xcurses4.2", "X/Open Curses Issue 4 Version 2 (\\(lqXCURSES4.2\\(rq)") +LINE("-susv2", "Version 2 of the Single UNIX Specification") +LINE("-susv3", "Version 3 of the Single UNIX Specification") +LINE("-svid4", "System V Interface Definition, Fourth Edition (\\(lqSVID4\\(rq)") diff --git a/usr.bin/mandoc/term.c b/usr.bin/mandoc/term.c new file mode 100644 index 0000000000..8d5f71f3f6 --- /dev/null +++ b/usr.bin/mandoc/term.c @@ -0,0 +1,633 @@ +/* $Id: term.c,v 1.17 2009/10/24 13:13:20 schwarze Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include +#include +#include +#include + +#include "chars.h" +#include "out.h" +#include "term.h" +#include "man.h" +#include "mdoc.h" +#include "main.h" + +/* FIXME: accomodate non-breaking, non-collapsing white-space. */ +/* FIXME: accomodate non-breaking, collapsing white-space. */ + +static struct termp *term_alloc(enum termenc); +static void term_free(struct termp *); + +static void do_escaped(struct termp *, const char **); +static void do_special(struct termp *, + const char *, size_t); +static void do_reserved(struct termp *, + const char *, size_t); +static void buffer(struct termp *, char); +static void encode(struct termp *, char); + + +void * +ascii_alloc(void) +{ + + return(term_alloc(TERMENC_ASCII)); +} + + +void +terminal_free(void *arg) +{ + + term_free((struct termp *)arg); +} + + +static void +term_free(struct termp *p) +{ + + if (p->buf) + free(p->buf); + if (p->symtab) + chars_free(p->symtab); + + free(p); +} + + +static struct termp * +term_alloc(enum termenc enc) +{ + struct termp *p; + + if (NULL == (p = malloc(sizeof(struct termp)))) + return(NULL); + bzero(p, sizeof(struct termp)); + p->maxrmargin = 78; + p->enc = enc; + return(p); +} + + +/* + * Flush a line of text. A "line" is loosely defined as being something + * that should be followed by a newline, regardless of whether it's + * broken apart by newlines getting there. A line can also be a + * fragment of a columnar list. + * + * Specifically, a line is whatever's in p->buf of length p->col, which + * is zeroed after this function returns. + * + * The usage of termp:flags is as follows: + * + * - TERMP_NOLPAD: when beginning to write the line, don't left-pad the + * offset value. This is useful when doing columnar lists where the + * prior column has right-padded. + * + * - TERMP_NOBREAK: this is the most important and is used when making + * columns. In short: don't print a newline and instead pad to the + * right margin. Used in conjunction with TERMP_NOLPAD. + * + * - TERMP_TWOSPACE: when padding, make sure there are at least two + * space characters of padding. Otherwise, rather break the line. + * + * - TERMP_DANGLE: don't newline when TERMP_NOBREAK is specified and + * the line is overrun, and don't pad-right if it's underrun. + * + * - TERMP_HANG: like TERMP_DANGLE, but doesn't newline when + * overruning, instead save the position and continue at that point + * when the next invocation. + * + * In-line line breaking: + * + * If TERMP_NOBREAK is specified and the line overruns the right + * margin, it will break and pad-right to the right margin after + * writing. If maxrmargin is violated, it will break and continue + * writing from the right-margin, which will lead to the above + * scenario upon exit. + * + * Otherwise, the line will break at the right margin. Extremely long + * lines will cause the system to emit a warning (TODO: hyphenate, if + * possible). + */ +void +term_flushln(struct termp *p) +{ + int i, j; + size_t vbl, vsz, vis, maxvis, mmax, bp; + static int overstep = 0; + + /* + * First, establish the maximum columns of "visible" content. + * This is usually the difference between the right-margin and + * an indentation, but can be, for tagged lists or columns, a + * small set of values. + */ + + assert(p->offset < p->rmargin); + assert((int)(p->rmargin - p->offset) - overstep > 0); + + maxvis = /* LINTED */ + p->rmargin - p->offset - overstep; + mmax = /* LINTED */ + p->maxrmargin - p->offset - overstep; + + bp = TERMP_NOBREAK & p->flags ? mmax : maxvis; + vis = 0; + + /* + * If in the standard case (left-justified), then begin with our + * indentation, otherwise (columns, etc.) just start spitting + * out text. + */ + + if ( ! (p->flags & TERMP_NOLPAD)) + /* LINTED */ + for (j = 0; j < (int)p->offset; j++) + putchar(' '); + + for (i = 0; i < (int)p->col; i++) { + /* + * Count up visible word characters. Control sequences + * (starting with the CSI) aren't counted. A space + * generates a non-printing word, which is valid (the + * space is printed according to regular spacing rules). + */ + + /* LINTED */ + for (j = i, vsz = 0; j < (int)p->col; j++) { + if (j && ' ' == p->buf[j]) + break; + else if (8 == p->buf[j]) + vsz--; + else + vsz++; + } + + /* + * Choose the number of blanks to prepend: no blank at the + * beginning of a line, one between words -- but do not + * actually write them yet. + */ + vbl = (size_t)(0 == vis ? 0 : 1); + + /* + * Find out whether we would exceed the right margin. + * If so, break to the next line. (TODO: hyphenate) + * Otherwise, write the chosen number of blanks now. + */ + if (vis && vis + vbl + vsz > bp) { + putchar('\n'); + if (TERMP_NOBREAK & p->flags) { + for (j = 0; j < (int)p->rmargin; j++) + putchar(' '); + vis = p->rmargin - p->offset; + } else { + for (j = 0; j < (int)p->offset; j++) + putchar(' '); + vis = 0; + } + /* Remove the overstep width. */ + bp += overstep; + overstep = 0; + } else { + for (j = 0; j < (int)vbl; j++) + putchar(' '); + vis += vbl; + } + + /* + * Finally, write out the word. + */ + for ( ; i < (int)p->col; i++) { + if (' ' == p->buf[i]) + break; + putchar(p->buf[i]); + } + vis += vsz; + } + p->col = 0; + + if ( ! (TERMP_NOBREAK & p->flags)) { + putchar('\n'); + return; + } + + overstep = 0; + if (TERMP_HANG & p->flags) { + /* We need one blank after the tag. */ + overstep = /* LINTED */ + vis - maxvis + 1; + + /* + * Behave exactly the same way as groff: + * If we have overstepped the margin, temporarily move + * it to the right and flag the rest of the line to be + * shorter. + * If we landed right at the margin, be happy. + * If we are one step before the margin, temporarily + * move it one step LEFT and flag the rest of the line + * to be longer. + */ + if (overstep >= -1) { + assert((int)maxvis + overstep >= 0); + /* LINTED */ + maxvis += overstep; + } else + overstep = 0; + + } else if (TERMP_DANGLE & p->flags) + return; + + /* Right-pad. */ + if (maxvis > vis + /* LINTED */ + ((TERMP_TWOSPACE & p->flags) ? 1 : 0)) + for ( ; vis < maxvis; vis++) + putchar(' '); + else { /* ...or newline break. */ + putchar('\n'); + for (i = 0; i < (int)p->rmargin; i++) + putchar(' '); + } +} + + +/* + * A newline only breaks an existing line; it won't assert vertical + * space. All data in the output buffer is flushed prior to the newline + * assertion. + */ +void +term_newln(struct termp *p) +{ + + p->flags |= TERMP_NOSPACE; + if (0 == p->col) { + p->flags &= ~TERMP_NOLPAD; + return; + } + term_flushln(p); + p->flags &= ~TERMP_NOLPAD; +} + + +/* + * Asserts a vertical space (a full, empty line-break between lines). + * Note that if used twice, this will cause two blank spaces and so on. + * All data in the output buffer is flushed prior to the newline + * assertion. + */ +void +term_vspace(struct termp *p) +{ + + term_newln(p); + putchar('\n'); +} + + +static void +do_special(struct termp *p, const char *word, size_t len) +{ + const char *rhs; + size_t sz; + int i; + + rhs = chars_a2ascii(p->symtab, word, len, &sz); + + if (NULL == rhs) { +#if 0 + fputs("Unknown special character: ", stderr); + for (i = 0; i < (int)len; i++) + fputc(word[i], stderr); + fputc('\n', stderr); +#endif + return; + } + for (i = 0; i < (int)sz; i++) + encode(p, rhs[i]); +} + + +static void +do_reserved(struct termp *p, const char *word, size_t len) +{ + const char *rhs; + size_t sz; + int i; + + rhs = chars_a2res(p->symtab, word, len, &sz); + + if (NULL == rhs) { +#if 0 + fputs("Unknown reserved word: ", stderr); + for (i = 0; i < (int)len; i++) + fputc(word[i], stderr); + fputc('\n', stderr); +#endif + return; + } + for (i = 0; i < (int)sz; i++) + encode(p, rhs[i]); +} + + +/* + * Handle an escape sequence: determine its length and pass it to the + * escape-symbol look table. Note that we assume mdoc(3) has validated + * the escape sequence (we assert upon badly-formed escape sequences). + */ +static void +do_escaped(struct termp *p, const char **word) +{ + int j, type; + const char *wp; + + wp = *word; + type = 1; + + if (0 == *(++wp)) { + *word = wp; + return; + } + + if ('(' == *wp) { + wp++; + if (0 == *wp || 0 == *(wp + 1)) { + *word = 0 == *wp ? wp : wp + 1; + return; + } + + do_special(p, wp, 2); + *word = ++wp; + return; + + } else if ('*' == *wp) { + if (0 == *(++wp)) { + *word = wp; + return; + } + + switch (*wp) { + case ('('): + wp++; + if (0 == *wp || 0 == *(wp + 1)) { + *word = 0 == *wp ? wp : wp + 1; + return; + } + + do_reserved(p, wp, 2); + *word = ++wp; + return; + case ('['): + type = 0; + break; + default: + do_reserved(p, wp, 1); + *word = wp; + return; + } + + } else if ('f' == *wp) { + if (0 == *(++wp)) { + *word = wp; + return; + } + + switch (*wp) { + case ('B'): + p->bold++; + break; + case ('I'): + p->under++; + break; + case ('P'): + /* FALLTHROUGH */ + case ('R'): + p->bold = p->under = 0; + break; + default: + break; + } + + *word = wp; + return; + + } else if ('[' != *wp) { + do_special(p, wp, 1); + *word = wp; + return; + } + + wp++; + for (j = 0; *wp && ']' != *wp; wp++, j++) + /* Loop... */ ; + + if (0 == *wp) { + *word = wp; + return; + } + + if (type) + do_special(p, wp - j, (size_t)j); + else + do_reserved(p, wp - j, (size_t)j); + *word = wp; +} + + +/* + * Handle pwords, partial words, which may be either a single word or a + * phrase that cannot be broken down (such as a literal string). This + * handles word styling. + */ +void +term_word(struct termp *p, const char *word) +{ + const char *sv; + + sv = word; + + if (word[0] && 0 == word[1]) + switch (word[0]) { + case('.'): + /* FALLTHROUGH */ + case(','): + /* FALLTHROUGH */ + case(';'): + /* FALLTHROUGH */ + case(':'): + /* FALLTHROUGH */ + case('?'): + /* FALLTHROUGH */ + case('!'): + /* FALLTHROUGH */ + case(')'): + /* FALLTHROUGH */ + case(']'): + /* FALLTHROUGH */ + case('}'): + if ( ! (TERMP_IGNDELIM & p->flags)) + p->flags |= TERMP_NOSPACE; + break; + default: + break; + } + + if ( ! (TERMP_NOSPACE & p->flags)) + buffer(p, ' '); + + if ( ! (p->flags & TERMP_NONOSPACE)) + p->flags &= ~TERMP_NOSPACE; + + for ( ; *word; word++) + if ('\\' != *word) + encode(p, *word); + else + do_escaped(p, &word); + + if (sv[0] && 0 == sv[1]) + switch (sv[0]) { + case('('): + /* FALLTHROUGH */ + case('['): + /* FALLTHROUGH */ + case('{'): + p->flags |= TERMP_NOSPACE; + break; + default: + break; + } +} + + +/* + * Insert a single character into the line-buffer. If the buffer's + * space is exceeded, then allocate more space by doubling the buffer + * size. + */ +static void +buffer(struct termp *p, char c) +{ + size_t s; + + if (p->col + 1 >= p->maxcols) { + if (0 == p->maxcols) + p->maxcols = 256; + s = p->maxcols * 2; + p->buf = realloc(p->buf, s); + if (NULL == p->buf) + err(1, "realloc"); /* FIXME: shouldn't be here! */ + p->maxcols = s; + } + p->buf[(int)(p->col)++] = c; +} + + +static void +encode(struct termp *p, char c) +{ + + if (' ' != c) { + if (p->bold) { + buffer(p, c); + buffer(p, 8); + } + if (p->under) { + buffer(p, '_'); + buffer(p, 8); + } + } + buffer(p, c); +} + + +size_t +term_vspan(const struct roffsu *su) +{ + double r; + + switch (su->unit) { + case (SCALE_CM): + r = su->scale * 2; + break; + case (SCALE_IN): + r = su->scale * 6; + break; + case (SCALE_PC): + r = su->scale; + break; + case (SCALE_PT): + r = su->scale / 8; + break; + case (SCALE_MM): + r = su->scale / 1000; + break; + case (SCALE_VS): + r = su->scale; + break; + default: + r = su->scale - 1; + break; + } + + if (r < 0.0) + r = 0.0; + return(/* LINTED */(size_t) + r); +} + + +size_t +term_hspan(const struct roffsu *su) +{ + double r; + + /* XXX: CM, IN, and PT are approximations. */ + + switch (su->unit) { + case (SCALE_CM): + r = 4 * su->scale; + break; + case (SCALE_IN): + /* XXX: this is an approximation. */ + r = 10 * su->scale; + break; + case (SCALE_PC): + r = (10 * su->scale) / 6; + break; + case (SCALE_PT): + r = (10 * su->scale) / 72; + break; + case (SCALE_MM): + r = su->scale / 1000; /* FIXME: double-check. */ + break; + case (SCALE_VS): + r = su->scale * 2 - 1; /* FIXME: double-check. */ + break; + default: + r = su->scale; + break; + } + + if (r < 0.0) + r = 0.0; + return((size_t)/* LINTED */ + r); +} diff --git a/usr.bin/mandoc/term.h b/usr.bin/mandoc/term.h new file mode 100644 index 0000000000..594f99959d --- /dev/null +++ b/usr.bin/mandoc/term.h @@ -0,0 +1,61 @@ +/* $Id: term.h,v 1.12 2009/10/21 19:13:51 schwarze Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef TERM_H +#define TERM_H + +__BEGIN_DECLS + +enum termenc { + TERMENC_ASCII +}; + +struct termp { + size_t rmargin; /* Current right margin. */ + size_t maxrmargin; /* Max right margin. */ + size_t maxcols; /* Max size of buf. */ + size_t offset; /* Margin offest. */ + size_t col; /* Bytes in buf. */ + int flags; +#define TERMP_NOSPACE (1 << 2) /* No space before words. */ +#define TERMP_NOLPAD (1 << 3) /* See term_flushln(). */ +#define TERMP_NOBREAK (1 << 4) /* See term_flushln(). */ +#define TERMP_IGNDELIM (1 << 6) /* Delims like regulars. */ +#define TERMP_NONOSPACE (1 << 7) /* No space (no autounset). */ +#define TERMP_DANGLE (1 << 8) /* See term_flushln(). */ +#define TERMP_HANG (1 << 9) /* See term_flushln(). */ +#define TERMP_TWOSPACE (1 << 10) /* See term_flushln(). */ +#define TERMP_NOSPLIT (1 << 11) /* See termp_an_pre/post(). */ +#define TERMP_SPLIT (1 << 12) /* See termp_an_pre/post(). */ +#define TERMP_ANPREC (1 << 13) /* See termp_an_pre(). */ + int bold; + int under; + char *buf; /* Output buffer. */ + enum termenc enc; /* Type of encoding. */ + void *symtab; /* Encoded-symbol table. */ +}; + +void term_newln(struct termp *); +void term_vspace(struct termp *); +void term_word(struct termp *, const char *); +void term_flushln(struct termp *); + +size_t term_hspan(const struct roffsu *); +size_t term_vspan(const struct roffsu *); + +__END_DECLS + +#endif /*!TERM_H*/ diff --git a/usr.bin/mandoc/tree.c b/usr.bin/mandoc/tree.c new file mode 100644 index 0000000000..b243df2a24 --- /dev/null +++ b/usr.bin/mandoc/tree.c @@ -0,0 +1,208 @@ +/* $Id: tree.c,v 1.5 2009/10/21 19:13:51 schwarze Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include +#include +#include + +#include "mdoc.h" +#include "man.h" +#include "main.h" + +static void print_mdoc(const struct mdoc_node *, int); +static void print_man(const struct man_node *, int); + + +/* ARGSUSED */ +void +tree_mdoc(void *arg, const struct mdoc *mdoc) +{ + + print_mdoc(mdoc_node(mdoc), 0); +} + + +/* ARGSUSED */ +void +tree_man(void *arg, const struct man *man) +{ + + print_man(man_node(man), 0); +} + + +static void +print_mdoc(const struct mdoc_node *n, int indent) +{ + const char *p, *t; + int i, j; + size_t argc, sz; + char **params; + struct mdoc_argv *argv; + + argv = NULL; + argc = sz = 0; + params = NULL; + + switch (n->type) { + case (MDOC_ROOT): + t = "root"; + break; + case (MDOC_BLOCK): + t = "block"; + break; + case (MDOC_HEAD): + t = "block-head"; + break; + case (MDOC_BODY): + t = "block-body"; + break; + case (MDOC_TAIL): + t = "block-tail"; + break; + case (MDOC_ELEM): + t = "elem"; + break; + case (MDOC_TEXT): + t = "text"; + break; + default: + abort(); + /* NOTREACHED */ + } + + switch (n->type) { + case (MDOC_TEXT): + p = n->string; + break; + case (MDOC_BODY): + p = mdoc_macronames[n->tok]; + break; + case (MDOC_HEAD): + p = mdoc_macronames[n->tok]; + break; + case (MDOC_TAIL): + p = mdoc_macronames[n->tok]; + break; + case (MDOC_ELEM): + p = mdoc_macronames[n->tok]; + if (n->args) { + argv = n->args->argv; + argc = n->args->argc; + } + break; + case (MDOC_BLOCK): + p = mdoc_macronames[n->tok]; + if (n->args) { + argv = n->args->argv; + argc = n->args->argc; + } + break; + case (MDOC_ROOT): + p = "root"; + break; + default: + abort(); + /* NOTREACHED */ + } + + for (i = 0; i < indent; i++) + (void)printf(" "); + (void)printf("%s (%s)", p, t); + + for (i = 0; i < (int)argc; i++) { + (void)printf(" -%s", mdoc_argnames[argv[i].arg]); + if (argv[i].sz > 0) + (void)printf(" ["); + for (j = 0; j < (int)argv[i].sz; j++) + (void)printf(" [%s]", argv[i].value[j]); + if (argv[i].sz > 0) + (void)printf(" ]"); + } + + for (i = 0; i < (int)sz; i++) + (void)printf(" [%s]", params[i]); + + (void)printf(" %d:%d\n", n->line, n->pos); + + if (n->child) + print_mdoc(n->child, indent + 1); + if (n->next) + print_mdoc(n->next, indent); +} + + +static void +print_man(const struct man_node *n, int indent) +{ + const char *p, *t; + int i; + + switch (n->type) { + case (MAN_ROOT): + t = "root"; + break; + case (MAN_ELEM): + t = "elem"; + break; + case (MAN_TEXT): + t = "text"; + break; + case (MAN_BLOCK): + t = "block"; + break; + case (MAN_HEAD): + t = "block-head"; + break; + case (MAN_BODY): + t = "block-body"; + break; + default: + abort(); + /* NOTREACHED */ + } + + switch (n->type) { + case (MAN_TEXT): + p = n->string; + break; + case (MAN_ELEM): + /* FALLTHROUGH */ + case (MAN_BLOCK): + /* FALLTHROUGH */ + case (MAN_HEAD): + /* FALLTHROUGH */ + case (MAN_BODY): + p = man_macronames[n->tok]; + break; + case (MAN_ROOT): + p = "root"; + break; + default: + abort(); + /* NOTREACHED */ + } + + for (i = 0; i < indent; i++) + (void)printf(" "); + (void)printf("%s (%s) %d:%d\n", p, t, n->line, n->pos); + + if (n->child) + print_man(n->child, indent + 1); + if (n->next) + print_man(n->next, indent); +} diff --git a/usr.bin/mandoc/vol.c b/usr.bin/mandoc/vol.c new file mode 100644 index 0000000000..9c0eae8c73 --- /dev/null +++ b/usr.bin/mandoc/vol.c @@ -0,0 +1,32 @@ +/* $Id: vol.c,v 1.2 2009/06/14 23:00:57 schwarze Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include + +#include "libmdoc.h" + +#define LINE(x, y) \ + if (0 == strcmp(p, x)) return(y); + +const char * +mdoc_a2vol(const char *p) +{ + +#include "vol.in" + + return(NULL); +} diff --git a/usr.bin/mandoc/vol.in b/usr.bin/mandoc/vol.in new file mode 100644 index 0000000000..4f2d346a3c --- /dev/null +++ b/usr.bin/mandoc/vol.in @@ -0,0 +1,35 @@ +/* $Id: vol.in,v 1.2 2009/06/14 23:00:57 schwarze Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This file defines volume titles for .Dt. + * + * Be sure to escape strings. + */ + +LINE("USD", "User\'s Supplementary Documents") +LINE("PS1", "Programmer\'s Supplementary Documents") +LINE("AMD", "Ancestral Manual Documents") +LINE("SMM", "System Manager\'s Manual") +LINE("URM", "User\'s Reference Manual") +LINE("PRM", "Programmer\'s Manual") +LINE("KM", "Kernel Manual") +LINE("IND", "Manual Master Index") +LINE("MMI", "Manual Master Index") +LINE("LOCAL", "Local Manual") +LINE("LOC", "Local Manual") +LINE("CON", "Contributed Software Manual") -- 2.41.0