diff options
author | 2009-04-06 20:30:40 +0000 | |
---|---|---|
committer | 2009-04-06 20:30:40 +0000 | |
commit | f73abda98a4c5213d9349b368e2be8a3b49b5b74 (patch) | |
tree | df64e8a874e335669bc9a8bfd2c0c95183ccca2c | |
parent | sync with 0.9.8k; (diff) | |
download | wireguard-openbsd-f73abda98a4c5213d9349b368e2be8a3b49b5b74.tar.xz wireguard-openbsd-f73abda98a4c5213d9349b368e2be8a3b49b5b74.zip |
Initial check-in of mandoc for formatting manuals. ok deraadt@
44 files changed, 15103 insertions, 0 deletions
diff --git a/usr.bin/mandoc/Makefile b/usr.bin/mandoc/Makefile new file mode 100644 index 00000000000..26886be5de0 --- /dev/null +++ b/usr.bin/mandoc/Makefile @@ -0,0 +1,13 @@ +# $OpenBSD: Makefile,v 1.1 2009/04/06 20:30:40 kristaps Exp $ + +CFLAGS+=-W -Wall -Wstrict-prototypes -Wno-unused-parameter + +SRCS= 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 +SRCS+= main.c mdoc_term.c ascii.c term.c tree.c man_term.c + +PROG= mandoc + +.include <bsd.prog.mk> diff --git a/usr.bin/mandoc/arch.c b/usr.bin/mandoc/arch.c new file mode 100644 index 00000000000..84a41f05b9b --- /dev/null +++ b/usr.bin/mandoc/arch.c @@ -0,0 +1,34 @@ +/* $Id: arch.c,v 1.1 2009/04/06 20:30:40 kristaps Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons <kristaps@openbsd.org> + * + * 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 <stdlib.h> +#include <string.h> + +#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 00000000000..390e370f55e --- /dev/null +++ b/usr.bin/mandoc/arch.in @@ -0,0 +1,54 @@ +/* $Id: arch.in,v 1.1 2009/04/06 20:30:40 kristaps Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons <kristaps@openbsd.org> + * + * 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/ascii.c b/usr.bin/mandoc/ascii.c new file mode 100644 index 00000000000..91f167e09e8 --- /dev/null +++ b/usr.bin/mandoc/ascii.c @@ -0,0 +1,191 @@ +/* $Id: ascii.c,v 1.1 2009/04/06 20:30:40 kristaps Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons <kristaps@openbsd.org> + * + * 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 <assert.h> +#include <err.h> +#include <stdlib.h> +#include <string.h> + +#include "term.h" + +#define ASCII_PRINT_HI 126 +#define ASCII_PRINT_LO 32 + +struct line { + const char *code; + const char *out; + /* 32- and 64-bit alignment safe. */ + size_t codesz; + size_t outsz; +}; + +struct linep { + const struct line *line; + struct linep *next; +}; + +#define LINE(w, x, y, z) \ + { (w), (y), (x), (z) }, +static const struct line lines[] = { +#include "ascii.in" +}; + +struct asciitab { + struct linep *lines; + void **htab; +}; + + +static inline int match(const struct line *, + const char *, size_t); + + +void +term_asciifree(void *arg) +{ + struct asciitab *tab; + + tab = (struct asciitab *)arg; + + free(tab->lines); + free(tab->htab); + free(tab); +} + + +void * +term_ascii2htab(void) +{ + struct asciitab *tab; + void **htab; + struct linep *pp, *p; + int i, len, 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 asciitab)))) + err(1, "malloc"); + + len = sizeof(lines) / sizeof(struct line); + + if (NULL == (p = calloc((size_t)len, sizeof(struct linep)))) + err(1, "malloc"); + + htab = calloc(ASCII_PRINT_HI - ASCII_PRINT_LO + 1, + sizeof(struct linep **)); + + if (NULL == htab) + err(1, "malloc"); + + for (i = 0; i < len; i++) { + assert(lines[i].codesz > 0); + assert(lines[i].code); + assert(lines[i].out); + + p[i].line = &lines[i]; + + hash = (int)lines[i].code[0] - ASCII_PRINT_LO; + + if (NULL == (pp = ((struct linep **)htab)[hash])) { + htab[hash] = &p[i]; + continue; + } + + for ( ; pp->next; pp = pp->next) + /* Scan ahead. */ ; + + pp->next = &p[i]; + } + + tab->htab = htab; + tab->lines = p; + + return(tab); +} + + +const char * +term_a2ascii(void *arg, const char *p, size_t sz, size_t *rsz) +{ + struct asciitab *tab; + struct linep *pp, *prev; + void **htab; + int hash; + + tab = (struct asciitab *)arg; + htab = tab->htab; + + assert(p); + assert(sz > 0); + + if (p[0] < ASCII_PRINT_LO || p[0] > ASCII_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] - ASCII_PRINT_LO; + + if (NULL == (pp = ((struct linep **)htab)[hash])) + return(NULL); + + if (NULL == pp->next) { + if ( ! match(pp->line, p, sz)) + return(NULL); + *rsz = pp->line->outsz; + return(pp->line->out); + } + + for (prev = NULL; pp; pp = pp->next) { + if ( ! match(pp->line, p, sz)) { + prev = pp; + continue; + } + + /* Re-order the hash chain. */ + + if (prev) { + prev->next = pp->next; + pp->next = ((struct linep **)htab)[hash]; + htab[hash] = pp; + } + + *rsz = pp->line->outsz; + return(pp->line->out); + } + + return(NULL); +} + + +static inline int +match(const struct line *line, const char *p, size_t sz) +{ + + if (line->codesz != sz) + return(0); + return(0 == strncmp(line->code, p, sz)); +} diff --git a/usr.bin/mandoc/ascii.in b/usr.bin/mandoc/ascii.in new file mode 100644 index 00000000000..924a43e40c3 --- /dev/null +++ b/usr.bin/mandoc/ascii.in @@ -0,0 +1,219 @@ +/* $Id: ascii.in,v 1.1 2009/04/06 20:30:40 kristaps Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons <kristaps@openbsd.org> + * + * 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 table. The left-hand side corresponds to the + * escape sequence (\x, \(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. + * + * Be sure to escape strings. + */ + +LINE("\\", 1, "\\", 1) +LINE("\'", 1, "\'", 1) +LINE("`", 1, "`", 1) +LINE("%", 1, "%", 1) +LINE("-", 1, "-", 1) +LINE(" ", 1, " ", 1) +LINE("0", 1, " ", 1) +LINE(".", 1, ".", 1) +LINE("&", 1, "", 0) +LINE("e", 1, "\\", 1) +LINE("q", 1, "\"", 1) +LINE("|", 1, "", 0) +LINE("rC", 2, "}", 1) +LINE("lC", 2, "{", 1) +LINE("rB", 2, "]", 1) +LINE("lB", 2, "[", 1) +LINE("ra", 2, ">", 1) +LINE("la", 2, "<", 1) +LINE("Lq", 2, "``", 2) +LINE("lq", 2, "``", 2) +LINE("Rq", 2, "\'\'", 2) +LINE("rq", 2, "\'\'", 2) +LINE("oq", 2, "`", 1) +LINE("aq", 2, "\'", 1) +LINE("Bq", 2, ",,", 2) +LINE("bq", 2, ",,", 2) +LINE("<-", 2, "<-", 2) +LINE("->", 2, "->", 2) +LINE("<>", 2, "<>", 2) +LINE("ua", 2, "^", 1) +LINE("da", 2, "v", 1) +LINE("bu", 2, "o", 1) +LINE("ci", 2, "O", 1) +LINE("Ba", 2, "|", 1) +LINE("ba", 2, "|", 1) +LINE("bb", 2, "|", 1) +LINE("co", 2, "(C)", 3) +LINE("rg", 2, "(R)", 3) +LINE("tm", 2, "tm", 2) +LINE("Am", 2, "&", 1) +LINE("Le", 2, "<=", 2) +LINE("<=", 2, "<=", 2) +LINE("Ge", 2, ">=", 2) +LINE(">=", 2, ">=", 2) +LINE("==", 2, "==", 2) +LINE("Ne", 2, "!=", 2) +LINE("!=", 2, "!=", 2) +LINE("Pm", 2, "+-", 2) +LINE("+-", 2, "+-", 2) +LINE("If", 2, "infinity", 8) +LINE("if", 2, "oo", 2) +LINE("Na", 2, "NaN", 3) +LINE("na", 2, "NaN", 3) +LINE("**", 2, "*", 1) +LINE("Gt", 2, ">", 1) +LINE("Lt", 2, "<", 1) +LINE("aa", 2, "\'", 1) +LINE("a~", 2, "~", 1) +LINE("ga", 2, "`", 1) +LINE("en", 2, "-", 1) +LINE("em", 2, "--", 2) +LINE("Pi", 2, "pi", 2) +LINE("Fo", 2, "<<", 2) +LINE("Fc", 2, ">>", 2) +LINE("fo", 2, "<", 1) +LINE("fc", 2, ">", 1) +LINE("lh", 2, "<=", 2) +LINE("rh", 2, "=>", 2) +LINE("ae", 2, "ae", 2) +LINE("AE", 2, "AE", 2) +LINE("oe", 2, "oe", 2) +LINE("OE", 2, "OE", 2) +LINE("ss", 2, "B", 1) +LINE("\'A", 2, "A", 1) +LINE("\'E", 2, "E", 1) +LINE("\'I", 2, "I", 1) +LINE("\'O", 2, "O", 1) +LINE("\'U", 2, "U", 1) +LINE("\'a", 2, "a", 1) +LINE("\'e", 2, "e", 1) +LINE("\'i", 2, "i", 1) +LINE("\'o", 2, "o", 1) +LINE("\'u", 2, "u", 1) +LINE("`A", 2, "A", 1) +LINE("`E", 2, "E", 1) +LINE("`I", 2, "I", 1) +LINE("`O", 2, "O", 1) +LINE("`U", 2, "U", 1) +LINE("`a", 2, "a", 1) +LINE("`e", 2, "e", 1) +LINE("`i", 2, "i", 1) +LINE("`o", 2, "o", 1) +LINE("`u", 2, "u", 1) +LINE("~A", 2, "A", 1) +LINE("~N", 2, "N", 1) +LINE("~O", 2, "O", 1) +LINE("~a", 2, "a", 1) +LINE("~n", 2, "n", 1) +LINE("~o", 2, "o", 1) +LINE("lA", 2, "<=", 2) +LINE("rA", 2, "=>", 2) +LINE("uA", 2, "^", 1) +LINE("dA", 2, "v", 1) +LINE("hA", 2, "<=>", 3) +LINE(":A", 2, "A", 1) +LINE(":E", 2, "E", 1) +LINE(":I", 2, "I", 1) +LINE(":O", 2, "O", 1) +LINE(":U", 2, "U", 1) +LINE(":a", 2, "a", 1) +LINE(":e", 2, "e", 1) +LINE(":i", 2, "i", 1) +LINE(":o", 2, "o", 1) +LINE(":u", 2, "u", 1) +LINE(":y", 2, "y", 1) +LINE("^A", 2, "A", 1) +LINE("^E", 2, "E", 1) +LINE("^I", 2, "I", 1) +LINE("^O", 2, "O", 1) +LINE("^U", 2, "U", 1) +LINE("^a", 2, "a", 1) +LINE("^e", 2, "e", 1) +LINE("^i", 2, "i", 1) +LINE("^o", 2, "o", 1) +LINE("^u", 2, "u", 1) +LINE("-D", 2, "D", 1) +LINE("Sd", 2, "o", 1) +LINE("TP", 2, "b", 1) +LINE("Tp", 2, "b", 1) +LINE(",C", 2, "C", 1) +LINE(",c", 2, "c", 1) +LINE("/L", 2, "L", 1) +LINE("/l", 2, "l", 1) +LINE("/O", 2, "O", 1) +LINE("/o", 2, "o", 1) +LINE("oA", 2, "A", 1) +LINE("oa", 2, "a", 1) +LINE("a^", 2, "^", 1) +LINE("ac", 2, ",", 1) +LINE("ad", 2, "\"", 1) +LINE("ah", 2, "v", 1) +LINE("ao", 2, "o", 1) +LINE("ho", 2, ",", 1) +LINE("ab", 2, "`", 1) +LINE("a\"", 2, "\"", 1) +LINE("a-", 2, "-", 1) +LINE("Cs", 2, "x", 1) +LINE("Do", 2, "$", 1) +LINE("Po", 2, "L", 1) +LINE("Ye", 2, "Y", 1) +LINE("Fn", 2, "f", 1) +LINE("ct", 2, "c", 1) +LINE("ff", 2, "ff", 2) +LINE("fi", 2, "fi", 2) +LINE("fl", 2, "fl", 2) +LINE("Fi", 2, "ffi", 3) +LINE("Fl", 2, "ffl", 3) +LINE("r!", 2, "i", 1) +LINE("r?", 2, "c", 1) +LINE("dd", 2, "=", 1) +LINE("dg", 2, "-", 1) +LINE("ps", 2, "9|", 2) +LINE("sc", 2, "S", 1) +LINE("de", 2, "o", 1) +LINE("tf", 2, ".:.", 3) +LINE("~~", 2, "~~", 2) +LINE("~=", 2, "~=", 2) +LINE("=~", 2, "=~", 2) +LINE("AN", 2, "^", 1) +LINE("OR", 2, "v", 1) +LINE("no", 2, "~", 1) +LINE("fa", 2, "V", 1) +LINE("te", 2, "3", 1) +LINE("Ah", 2, "N", 1) +LINE("Im", 2, "I", 1) +LINE("Re", 2, "R", 1) +LINE("mo", 2, "E", 1) +LINE("nm", 2, "E", 1) +LINE("eq", 2, "=", 1) +LINE("pl", 2, "+", 1) +LINE("di", 2, "-:-", 3) +LINE("mu", 2, "x", 1) +LINE("(=", 2, "(=", 2) +LINE("=)", 2, "=)", 2) +LINE("ap", 2, "~", 1) +LINE("pd", 2, "a", 1) +LINE("gr", 2, "V", 1) +LINE("ca", 2, "(^)", 3) +LINE("cu", 2, "U", 1) +LINE("es", 2, "{}", 2) +LINE("st", 2, "-)", 2) diff --git a/usr.bin/mandoc/att.c b/usr.bin/mandoc/att.c new file mode 100644 index 00000000000..ce47a8c9a15 --- /dev/null +++ b/usr.bin/mandoc/att.c @@ -0,0 +1,34 @@ +/* $Id: att.c,v 1.1 2009/04/06 20:30:40 kristaps Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons <kristaps@openbsd.org> + * + * 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 <stdlib.h> +#include <string.h> + +#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 00000000000..8d10ed72cbd --- /dev/null +++ b/usr.bin/mandoc/att.in @@ -0,0 +1,39 @@ +/* $Id: att.in,v 1.1 2009/04/06 20:30:40 kristaps Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons <kristaps@openbsd.org> + * + * 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/lib.c b/usr.bin/mandoc/lib.c new file mode 100644 index 00000000000..bcef6e593d0 --- /dev/null +++ b/usr.bin/mandoc/lib.c @@ -0,0 +1,34 @@ +/* $Id: lib.c,v 1.1 2009/04/06 20:30:40 kristaps Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons <kristaps@openbsd.org> + * + * 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 <stdlib.h> +#include <string.h> + +#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 00000000000..adf82a14de8 --- /dev/null +++ b/usr.bin/mandoc/lib.in @@ -0,0 +1,59 @@ +/* $Id: lib.in,v 1.1 2009/04/06 20:30:40 kristaps Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons <kristaps@openbsd.org> + * + * 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 00000000000..c1a82471ce0 --- /dev/null +++ b/usr.bin/mandoc/libman.h @@ -0,0 +1,62 @@ +/* $Id: libman.h,v 1.1 2009/04/06 20:30:40 kristaps Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons <kristaps@openbsd.org> + * + * 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; + void *htab; + int pflags; + int flags; +#define MAN_HALT (1 << 0) +#define MAN_NLINE (1 << 1) + enum man_next next; + struct man_node *last; + struct man_node *first; + struct man_meta meta; +}; + +__BEGIN_DECLS + +int man_word_alloc(struct man *, int, int, const char *); +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_alloc(void); +int man_macro(struct man *, int, + int, int, int *, char *); +int man_hash_find(const void *, const char *); +void man_hash_free(void *); +int man_macroend(struct man *); +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_action_post(struct man *); + +__END_DECLS + +#endif /*!LIBMAN_H*/ diff --git a/usr.bin/mandoc/libmdoc.h b/usr.bin/mandoc/libmdoc.h new file mode 100644 index 00000000000..fa0c996a066 --- /dev/null +++ b/usr.bin/mandoc/libmdoc.h @@ -0,0 +1,157 @@ +/* $Id: libmdoc.h,v 1.1 2009/04/06 20:30:40 kristaps Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@openbsd.org> + * + * 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; + void *htab; + int flags; +#define MDOC_HALT (1 << 0) +#define MDOC_LITERAL (1 << 1) + 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; +}; + + +#define MACRO_PROT_ARGS struct mdoc *mdoc, 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. */ +}; + +#define mdoc_nwarn(mdoc, node, type, fmt, ...) \ + mdoc_vwarn((mdoc), (node)->line, \ + (node)->pos, (type), (fmt), ##__VA_ARGS__) + +#define mdoc_nerr(mdoc, node, fmt, ...) \ + mdoc_verr((mdoc), (node)->line, \ + (node)->pos, (fmt), ##__VA_ARGS__) + +#define mdoc_warn(mdoc, type, fmt, ...) \ + mdoc_vwarn((mdoc), (mdoc)->last->line, \ + (mdoc)->last->pos, (type), (fmt), ##__VA_ARGS__) + +#define mdoc_err(mdoc, fmt, ...) \ + mdoc_verr((mdoc), (mdoc)->last->line, \ + (mdoc)->last->pos, (fmt), ##__VA_ARGS__) + +#define mdoc_msg(mdoc, fmt, ...) \ + mdoc_vmsg((mdoc), (mdoc)->last->line, \ + (mdoc)->last->pos, (fmt), ##__VA_ARGS__) + +#define mdoc_pmsg(mdoc, line, pos, fmt, ...) \ + mdoc_vmsg((mdoc), (line), \ + (pos), (fmt), ##__VA_ARGS__) + +#define mdoc_pwarn(mdoc, line, pos, type, fmt, ...) \ + mdoc_vwarn((mdoc), (line), \ + (pos), (type), (fmt), ##__VA_ARGS__) + +#define mdoc_perr(mdoc, line, pos, fmt, ...) \ + mdoc_verr((mdoc), (line), \ + (pos), (fmt), ##__VA_ARGS__) + +extern const struct mdoc_macro *const mdoc_macros; + +__BEGIN_DECLS + +int mdoc_vwarn(struct mdoc *, int, int, + enum mdoc_warn, const char *, ...); +void mdoc_vmsg(struct mdoc *, int, int, + const char *, ...); +int mdoc_verr(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_alloc(void); +int mdoc_hash_find(const void *, const char *); +void mdoc_hash_free(void *); +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_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 **); +#define ARGS_ERROR (-1) +#define ARGS_EOLN (0) +#define ARGS_WORD (1) +#define ARGS_PUNCT (2) +#define ARGS_QWORD (3) +#define ARGS_PHRASE (4) + +/* FIXME: get rid of these. */ +int xstrlcpys(char *, const struct mdoc_node *, size_t); +void *xrealloc(void *, size_t); +char *xstrdup(const char *); +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 00000000000..70b4aeb91e8 --- /dev/null +++ b/usr.bin/mandoc/main.c @@ -0,0 +1,671 @@ +/* $Id: main.c,v 1.1 2009/04/06 20:30:40 kristaps Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@openbsd.org> + * + * 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 <sys/stat.h> + +#include <assert.h> +#include <err.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "mdoc.h" +#include "man.h" + +typedef int (*out_mdoc)(void *, const struct mdoc *); +typedef int (*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_LINT +}; + +struct curparse { + const char *file; /* Current parse. */ + int fd; /* Current parse. */ + int wflags; +#define WARN_WALL 0x03 /* All-warnings mask. */ +#define WARN_WCOMPAT (1 << 0) /* Compatibility warnings. */ +#define WARN_WSYNTAX (1 << 1) /* Syntax warnings. */ +#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. */ + 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; +}; + +extern void *ascii_alloc(void); +extern int tree_mdoc(void *, const struct mdoc *); +extern int tree_man(void *, const struct man *); +extern int terminal_mdoc(void *, const struct mdoc *); +extern int terminal_man(void *, const struct man *); +extern void terminal_free(void *); + +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 manwarn(void *, int, int, const char *); +static int mdocwarn(void *, int, int, + enum mdoc_warn, const char *); +static int fstdin(struct buf *, struct buf *, + struct curparse *); +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 *); +__dead 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:W:T:"))) + switch (c) { + case ('f'): + if ( ! foptions(&curp.fflags, optarg)) + return(0); + break; + case ('m'): + if ( ! moptions(&curp.inttype, optarg)) + return(0); + break; + case ('T'): + if ( ! toptions(&curp.outtype, optarg)) + return(0); + break; + case ('W'): + if ( ! woptions(&curp.wflags, optarg)) + return(0); + break; + default: + usage(); + /* NOTREACHED */ + } + + argc -= optind; + argv += optind; + + /* Configure buffers. */ + + bzero(&ln, sizeof(struct buf)); + bzero(&blk, sizeof(struct buf)); + + rc = 1; + + if (NULL == *argv) + if ( ! fstdin(&blk, &ln, &curp)) + rc = 0; + + while (rc && *argv) { + if ( ! ffile(&blk, &ln, *argv, &curp)) + rc = 0; + 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); +} + + +__dead static void +usage(void) +{ + + (void)fprintf(stderr, "usage: %s [-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 = manwarn; + + pflags = MAN_IGN_MACRO; + + if (curp->fflags & NO_IGN_MACRO) + pflags &= ~MAN_IGN_MACRO; + + 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_msg = NULL; + mdoccb.mdoc_err = merr; + mdoccb.mdoc_warn = mdocwarn; + + 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 +fstdin(struct buf *blk, struct buf *ln, struct curparse *curp) +{ + + curp->file = "<stdin>"; + curp->fd = STDIN_FILENO; + return(fdesc(blk, ln, curp)); +} + + +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(0); + } + + 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; + 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)) + warnx("%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(0); + } + blk->sz = sz; + } + + /* Fill buf with file blocksize. */ + + for (lnn = 0, pos = 0; ; ) { + if (-1 == (ssz = read(curp->fd, blk->buf, sz))) { + warn("%s", curp->file); + return(0); + } 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(0); + } + } + + if ('\n' != blk->buf[i]) { + ln->buf[pos++] = blk->buf[i]; + continue; + } + + /* Check for CPP-escaped newline. */ + + if (pos > 0 && '\\' == 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 no manual parser has been assigned, then + * try to assign one in pset(), which may do + * nothing at all. After this, parse the manual + * line accordingly. + */ + + if ( ! (man || mdoc) && ! pset(ln->buf, + pos, curp, &man, &mdoc)) + return(0); + + pos = 0; + + if (man && ! man_parseln(man, lnn, ln->buf)) + return(0); + if (mdoc && ! mdoc_parseln(mdoc, lnn, ln->buf)) + return(0); + } + } + + /* Note that a parser may not have been assigned, yet. */ + + if ( ! (man || mdoc)) { + warnx("%s: not a manual", curp->file); + return(0); + } + + if (mdoc && ! mdoc_endparse(mdoc)) + return(0); + if (man && ! man_endparse(man)) + return(0); + + /* + * If an output device hasn't been allocated, see if we should + * do so now. Note that not all outtypes have functions, so + * this switch statement may be superfluous, but it's + * low-overhead enough not to matter very much. + */ + + if ( ! (curp->outman && curp->outmdoc)) { + switch (curp->outtype) { + 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) + if ( ! (*curp->outman)(curp->outdata, man)) + return(0); + if (mdoc && curp->outmdoc) + if ( ! (*curp->outmdoc)(curp->outdata, mdoc)) + return(0); + + return(1); +} + + +static int +pset(const char *buf, int pos, struct curparse *curp, + struct man **man, struct mdoc **mdoc) +{ + + /* + * 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 comments, use -mdoc when finding `.Dt', or + * default to -man, which is more lenient. + */ + + if (pos >= 3 && 0 == memcmp(buf, ".\\\"", 3)) + 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 { + warnx("bad argument: -T%s", arg); + return(0); + } + + return(1); +} + + +/* + * Parse out the options for [-fopt...] setting compiler options. These + * can be comma-delimited or called again. + */ +static int +foptions(int *fflags, char *arg) +{ + char *v; + char *toks[6]; + + toks[0] = "ign-scope"; + toks[1] = "no-ign-escape"; + toks[2] = "no-ign-macro"; + toks[3] = "no-ign-chars"; + toks[4] = "strict"; + toks[5] = NULL; + + while (*arg) + switch (getsubopt(&arg, 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 |= NO_IGN_ESCAPE | + NO_IGN_MACRO | NO_IGN_CHARS; + break; + default: + warnx("bad argument: -f%s", arg); + return(0); + } + + return(1); +} + + +/* + * Parse out the options for [-Werr...], which sets warning modes. + * These can be comma-delimited or called again. + */ +static int +woptions(int *wflags, char *arg) +{ + char *v; + char *toks[5]; + + toks[0] = "all"; + toks[1] = "compat"; + toks[2] = "syntax"; + toks[3] = "error"; + toks[4] = NULL; + + while (*arg) + switch (getsubopt(&arg, toks, &v)) { + case (0): + *wflags |= WARN_WALL; + break; + case (1): + *wflags |= WARN_WCOMPAT; + break; + case (2): + *wflags |= WARN_WSYNTAX; + break; + case (3): + *wflags |= WARN_WERR; + break; + default: + warnx("bad argument: -W%s", arg); + return(0); + } + + return(1); +} + + +/* ARGSUSED */ +static int +merr(void *arg, int line, int col, const char *msg) +{ + struct curparse *curp; + + curp = (struct curparse *)arg; + + warnx("%s:%d: error: %s (column %d)", + curp->file, line, msg, col); + return(0); +} + + +static int +mdocwarn(void *arg, int line, int col, + enum mdoc_warn type, const char *msg) +{ + struct curparse *curp; + char *wtype; + + curp = (struct curparse *)arg; + wtype = NULL; + + switch (type) { + case (WARN_COMPAT): + wtype = "compat"; + if (curp->wflags & WARN_WCOMPAT) + break; + return(1); + case (WARN_SYNTAX): + wtype = "syntax"; + if (curp->wflags & WARN_WSYNTAX) + break; + return(1); + } + + assert(wtype); + warnx("%s:%d: %s warning: %s (column %d)", + curp->file, line, wtype, msg, col); + + if ( ! (curp->wflags & WARN_WERR)) + return(1); + + warnx("%s: considering warnings as errors", + __progname); + return(0); +} + + +static int +manwarn(void *arg, int line, int col, const char *msg) +{ + struct curparse *curp; + + curp = (struct curparse *)arg; + + if ( ! (curp->wflags & WARN_WSYNTAX)) + return(1); + + warnx("%s:%d: syntax warning: %s (column %d)", + curp->file, line, msg, col); + + if ( ! (curp->wflags & WARN_WERR)) + return(1); + + warnx("%s: considering warnings as errors", + __progname); + return(0); +} diff --git a/usr.bin/mandoc/man.3 b/usr.bin/mandoc/man.3 new file mode 100644 index 00000000000..67a3026d04d --- /dev/null +++ b/usr.bin/mandoc/man.3 @@ -0,0 +1,279 @@ +.\" $Id: man.3,v 1.1 2009/04/06 20:30:40 kristaps Exp $ +.\" +.\" Copyright (c) 2009 Kristaps Dzonsons <kristaps@openbsd.org> +.\" +.\" 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: April 6 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 +.Fd #include <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 "struct man *man" +.Ft "const struct man_meta *" +.Fn man_meta "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 +.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@openbsd.org . diff --git a/usr.bin/mandoc/man.7 b/usr.bin/mandoc/man.7 new file mode 100644 index 00000000000..f70a15c0d6a --- /dev/null +++ b/usr.bin/mandoc/man.7 @@ -0,0 +1,205 @@ +.\" $Id: man.7,v 1.1 2009/04/06 20:30:40 kristaps Exp $ +.\" +.\" Copyright (c) 2009 Kristaps Dzonsons <kristaps@openbsd.org> +.\" +.\" 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: April 6 2009 $ +.Dt man 7 +.Os +.\" SECTION +.Sh NAME +.Nm man +.Nd man language reference +.\" SECTION +.Sh DESCRIPTION +The +.Nm man +language was historically used to format +.Ux +manuals. This reference document describes the syntax and structure of +this language. +.Pp +.Em \&Do not +use +.Nm +to write your manuals. Use the +.Xr mdoc 7 +language, instead. +.\" PARAGRAPH +.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 +.\" SECTION +.Sh INPUT ENCODING +.Nm +documents may contain only graphable 7-bit ASCII characters and the +space character +.Sq \ . +All manuals must have +.Ux +.Sq \en +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. +.\" SUB-SECTION +.Ss Special Characters +Special character 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. +.Pp +Characters may alternatively be escaped by a slash-asterisk, +.Sq \e* , +with the same combinations as described above. This form is deprecated. +.\" SECTION +.Sh STRUCTURE +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, +.Sq \&.PP +and +.Sq \&.\ \ \ \&PP +are equivalent. +.Pp +All +.Nm +macros follow the same structural rules: +.Bd -literal -offset indent +\&.YO \(lBbody...\(rB +.Ed +.Pp +The +.Dq body +consists of zero or more arguments to the macro. +.Pp +.Nm +has a primitive notion of multi-line scope for the following macros: +.Sq \&.TM , +.Sq \&.SM , +.Sq \&.SB , +.Sq \&.BI , +.Sq \&.IB , +.Sq \&.BR , +.Sq \&.RB , +.Sq \&.R , +.Sq \&.B , +.Sq \&.I , +.Sq \&.IR +and +.Sq \&.RI . +When these macros are invoked without arguments, the subsequent line is +considered a continuation of the macro. Thus: +.Bd -literal -offset indent +\&.RI +foo +.Ed +.Pp +is equivalent to +.Sq \&.RI foo . +If two consecutive lines exhibit the latter behaviour, +an error is raised. Thus, the following is not acceptable: +.Bd -literal -offset indent +\&.RI +\&.I +Hello, world. +.Ed +.Pp +The +.Sq \&.TP +macro is similar, but does not need an empty argument line to trigger +the behaviour. +.\" PARAGRAPH +.Sh MACROS +This section contains a complete list of all +.Nm +macros and corresponding number of arguments. +.Pp +.Bl -column "MacroX" "Arguments" -compact -offset indent +.It Em Macro Ta Em Arguments +.It \&.TH Ta >1, <6 +.It \&.SH Ta >0 +.It \&.SS Ta >0 +.It \&.TP Ta n +.It \&.LP Ta 0 +.It \&.PP Ta 0 +.It \&.P Ta 0 +.It \&.IP Ta <3 +.It \&.HP Ta <2 +.It \&.SM Ta n +.It \&.SB Ta n +.It \&.BI Ta n +.It \&.IB Ta n +.It \&.BR Ta n +.It \&.RB Ta n +.It \&.R Ta n +.It \&.B Ta n +.It \&.I Ta n +.It \&.IR Ta n +.It \&.RI Ta n +.El +.Pp +Although not historically part of the +.Nm +system, the following macros are also supported: +.Pp +.Bl -column "MacroX" "Arguments" -compact -offset indent +.It Em Macro Ta Em Arguments +.It \&.br Ta 0 +.It \&.i Ta n +.El +.Pp +These follow the same calling conventions as the above +.Nm +macros. +.\" SECTION +.Sh SEE ALSO +.Xr mandoc 1 , +.Xr mandoc_char 7 +.\" SECTION +.Sh AUTHORS +The +.Nm +utility was written by +.An Kristaps Dzonsons Aq kristaps@openbsd.org . +.\" SECTION +.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 00000000000..890955e972e --- /dev/null +++ b/usr.bin/mandoc/man.c @@ -0,0 +1,440 @@ +/* $Id: man.c,v 1.1 2009/04/06 20:30:40 kristaps Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@openbsd.org> + * + * 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 <assert.h> +#include <ctype.h> +#include <stdarg.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include "libman.h" + +const char *const __man_macronames[MAN_MAX] = { + "\\\"", "TH", "SH", "SS", + "TP", "LP", "PP", "P", + "IP", "HP", "SM", "SB", + "BI", "IB", "BR", "RB", + "R", "B", "I", "IR", + "RI", "br", "na", "i" + }; + +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 *); + + +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); + + if (man->htab) + man_hash_free(man->htab); + 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); + } + + p->data = data; + p->pflags = pflags; + (void)memcpy(&p->cb, cb, sizeof(struct man_cb)); + + if (NULL == (p->htab = man_hash_alloc())) { + free(p); + return(NULL); + } + 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 */ + } + + 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 *man, int line, int pos, int tok) +{ + struct man_node *p; + + p = man_node_alloc(line, pos, MAN_ELEM, tok); + if (NULL == p) + return(0); + return(man_node_append(man, p)); +} + + +int +man_word_alloc(struct man *man, + int line, int pos, const char *word) +{ + struct man_node *p; + + p = man_node_alloc(line, pos, MAN_TEXT, -1); + if (NULL == p) + return(0); + if (NULL == (p->string = strdup(word))) + return(0); + return(man_node_append(man, p)); +} + + +void +man_node_free(struct man_node *p) +{ + + if (p->string) + free(p->string); + free(p); +} + + +void +man_node_freelist(struct man_node *p) +{ + + if (p->child) + man_node_freelist(p->child); + if (p->next) + man_node_freelist(p->next); + + man_node_free(p); +} + + +static int +man_ptext(struct man *m, int line, char *buf) +{ + + if ( ! man_word_alloc(m, line, 0, buf)) + return(0); + m->next = MAN_NEXT_SIBLING; + + /* + * If this is one of the zany NLINE macros that consumes the + * next line of input as being influenced, then close out the + * existing macro "scope" and continue processing. + */ + + if ( ! (MAN_NLINE & m->flags)) + return(1); + + m->flags &= ~MAN_NLINE; + m->last = m->last->parent; + + assert(MAN_ROOT != m->last->type); + if ( ! man_valid_post(m)) + return(0); + if ( ! man_action_post(m)) + return(0); + + return(1); +} + + +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. */ + + n = m->last; + fl = MAN_NLINE & 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; + + if (buf[i] && '\\' == buf[i]) + if (buf[i + 1] && '\"' == buf[i + 1]) + goto out; + + /* 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; + } + + mac[j] = 0; + + if (j == 4 || j < 1) { + if ( ! (MAN_IGN_MACRO & m->pflags)) { + (void)man_verr(m, ln, ppos, + "ill-formed macro: %s", mac); + goto err; + } + if ( ! man_vwarn(m, ln, ppos, + "ill-formed macro: %s", mac)) + goto err; + return(1); + } + + if (MAN_MAX == (c = man_hash_find(m->htab, mac))) { + if ( ! (MAN_IGN_MACRO & m->pflags)) { + (void)man_verr(m, ln, ppos, + "unknown macro: %s", mac); + goto err; + } + if ( ! man_vwarn(m, ln, ppos, + "unknown macro: %s", mac)) + goto err; + return(1); + } + + /* The macro is sane. Jump to the next word. */ + + while (buf[i] && ' ' == buf[i]) + i++; + + /* Begin recursive parse sequence. */ + + if ( ! man_macro(m, c, ln, ppos, &i, buf)) + goto err; + +out: + if (fl) { + /* + * A NLINE macro has been immediately followed with + * another. Close out the preceeding macro's scope, and + * continue. + */ + assert(MAN_ROOT != m->last->type); + assert(m->last->parent); + assert(MAN_ROOT != m->last->parent->type); + + if (n != m->last) + m->last = m->last->parent; + + if ( ! man_valid_post(m)) + return(0); + if ( ! man_action_post(m)) + return(0); + m->next = MAN_NEXT_SIBLING; + m->flags &= ~MAN_NLINE; + } + + return(1); + +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)); +} + + diff --git a/usr.bin/mandoc/man.h b/usr.bin/mandoc/man.h new file mode 100644 index 00000000000..a2055213f09 --- /dev/null +++ b/usr.bin/mandoc/man.h @@ -0,0 +1,104 @@ +/* $Id: man.h,v 1.1 2009/04/06 20:30:40 kristaps Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons <kristaps@openbsd.org> + * + * 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 <time.h> + +#define MAN___ 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_br 21 +#define MAN_na 22 +#define MAN_i 23 +#define MAN_MAX 24 + +enum man_type { + MAN_TEXT, + MAN_ELEM, + MAN_ROOT +}; + +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 line; + int pos; + int tok; + int flags; +#define MAN_VALID (1 << 0) +#define MAN_ACTED (1 << 1) + enum man_type type; + char *string; +}; + +#define MAN_IGN_MACRO (1 << 0) /* Ignore unknown macros. */ + +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 *); +int man_valid_post(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 00000000000..df2b97b551d --- /dev/null +++ b/usr.bin/mandoc/man_action.c @@ -0,0 +1,192 @@ +/* $Id: man_action.c,v 1.1 2009/04/06 20:30:40 kristaps Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@openbsd.org> + * + * 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 <sys/utsname.h> + +#include <assert.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#include "libman.h" + +struct actions { + int (*post)(struct man *); +}; + + +static int post_TH(struct man *); +static time_t man_atotime(const char *); + +const struct actions man_actions[MAN_MAX] = { + { NULL }, /* __ */ + { 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 }, /* br */ + { NULL }, /* na */ + { NULL }, /* i */ +}; + + +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): + break; + case (MAN_ROOT): + break; + default: + if (NULL == man_actions[m->last->tok].post) + break; + return((*man_actions[m->last->tok].post)(m)); + } + 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_verr(m, n->line, n->pos, + "memory exhausted")); + + /* 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_vwarn(m, n->line, n->pos, "invalid section")) + 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_vwarn(m, n->line, n->pos, "invalid date")) + 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_verr(m, n->line, n->pos, + "memory exhausted")); + + /* TITLE MSEC DATE SOURCE ->VOL<- */ + + if (n && (n = n->next)) + if (NULL == (m->meta.vol = strdup(n->string))) + return(man_verr(m, n->line, n->pos, + "memory exhausted")); + + /* + * 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) { + assert(MAN_ROOT == m->last->parent->type); + m->last->parent->child = NULL; + n = m->last; + m->last = m->last->parent; + m->next = MAN_NEXT_CHILD; + assert(m->last == m->first); + } 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; + + (void)memset(&tm, 0, 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_hash.c b/usr.bin/mandoc/man_hash.c new file mode 100644 index 00000000000..29dda4b9e28 --- /dev/null +++ b/usr.bin/mandoc/man_hash.c @@ -0,0 +1,93 @@ +/* $Id: man_hash.c,v 1.1 2009/04/06 20:30:40 kristaps Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@openbsd.org> + * + * 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 <assert.h> +#include <stdlib.h> +#include <string.h> + +#include "libman.h" + + +/* ARGUSED */ +void +man_hash_free(void *htab) +{ + + free(htab); +} + + +/* ARGUSED */ +void * +man_hash_alloc(void) +{ + int *htab; + int i, j, x; + + htab = calloc(26 * 5, sizeof(int)); + if (NULL == htab) + return(NULL); + + for (i = 1; i < MAN_MAX; i++) { + x = man_macronames[i][0]; + + assert((x >= 65 && x <= 90) || + (x >= 97 && x <= 122)); + + x -= (x <= 90) ? 65 : 97; + x *= 5; + + for (j = 0; j < 5; j++) + if (0 == htab[x + j]) { + htab[x + j] = i; + break; + } + + assert(j < 5); + } + + return((void *)htab); +} + + +int +man_hash_find(const void *arg, const char *tmp) +{ + int x, i, tok; + const int *htab; + + htab = (const int *)arg; + + 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 *= 5; + + for (i = 0; i < 5; i++) { + if (0 == (tok = htab[x + i])) + return(MAN_MAX); + if (0 == strcmp(tmp, man_macronames[tok])) + return(tok); + } + + return(MAN_MAX); +} + diff --git a/usr.bin/mandoc/man_macro.c b/usr.bin/mandoc/man_macro.c new file mode 100644 index 00000000000..aba8677651b --- /dev/null +++ b/usr.bin/mandoc/man_macro.c @@ -0,0 +1,228 @@ +/* $Id: man_macro.c,v 1.1 2009/04/06 20:30:40 kristaps Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@openbsd.org> + * + * 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 <assert.h> +#include <ctype.h> +#include <stdlib.h> +#include <string.h> + +#include "libman.h" + +#define FL_NLINE (1 << 0) +#define FL_TLINE (1 << 1) + +static int man_args(struct man *, int, + int *, char *, char **); + +static int man_flags[MAN_MAX] = { + 0, /* __ */ + 0, /* TH */ + 0, /* SH */ + 0, /* SS */ + FL_TLINE, /* TP */ + 0, /* LP */ + 0, /* PP */ + 0, /* P */ + 0, /* IP */ + 0, /* HP */ + FL_NLINE, /* SM */ + FL_NLINE, /* SB */ + FL_NLINE, /* BI */ + FL_NLINE, /* IB */ + FL_NLINE, /* BR */ + FL_NLINE, /* RB */ + FL_NLINE, /* R */ + FL_NLINE, /* B */ + FL_NLINE, /* I */ + FL_NLINE, /* IR */ + FL_NLINE, /* RI */ + 0, /* br */ + 0, /* na */ + FL_NLINE, /* i */ +}; + +int +man_macro(struct man *man, int tok, int line, + int ppos, int *pos, char *buf) +{ + int w, la; + char *p; + struct man_node *n; + + if ( ! man_elem_alloc(man, line, ppos, tok)) + return(0); + n = man->last; + man->next = MAN_NEXT_CHILD; + + for (;;) { + la = *pos; + w = man_args(man, line, pos, buf, &p); + + if (-1 == w) + return(0); + if (0 == w) + break; + + if ( ! man_word_alloc(man, line, la, p)) + return(0); + man->next = MAN_NEXT_SIBLING; + } + + if (n == man->last && (FL_NLINE & man_flags[tok])) { + if (MAN_NLINE & man->flags) + return(man_verr(man, line, ppos, + "next-line scope already open")); + man->flags |= MAN_NLINE; + return(1); + } + + if (FL_TLINE & man_flags[tok]) { + if (MAN_NLINE & man->flags) + return(man_verr(man, line, ppos, + "next-line scope already open")); + man->flags |= MAN_NLINE; + 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 ( ; man->last; man->last = man->last->parent) { + if (man->last == n) + break; + if (man->last->type == MAN_ROOT) + break; + if ( ! man_valid_post(man)) + return(0); + if ( ! man_action_post(man)) + return(0); + } + + assert(man->last); + + /* + * Same here regarding whether we're back at the root. + */ + + if (man->last->type != MAN_ROOT && ! man_valid_post(man)) + return(0); + if (man->last->type != MAN_ROOT && ! man_action_post(man)) + return(0); + if (man->last->type != MAN_ROOT) + man->next = MAN_NEXT_SIBLING; + + return(1); +} + + +int +man_macroend(struct man *m) +{ + + for ( ; m->last && m->last != m->first; + m->last = m->last->parent) { + if ( ! man_valid_post(m)) + return(0); + if ( ! man_action_post(m)) + return(0); + } + assert(m->last == m->first); + + if ( ! man_valid_post(m)) + return(0); + if ( ! man_action_post(m)) + return(0); + + return(1); +} + + +/* ARGSUSED */ +static int +man_args(struct man *m, int line, + int *pos, char *buf, char **v) +{ + + if (0 == buf[*pos]) + return(0); + + /* First parse non-quoted strings. */ + + if ('\"' != buf[*pos]) { + *v = &buf[*pos]; + + while (buf[*pos]) { + if (' ' == buf[*pos]) + if ('\\' != buf[*pos - 1]) + break; + (*pos)++; + } + + if (0 == buf[*pos]) + return(1); + + buf[(*pos)++] = 0; + + if (0 == buf[*pos]) + return(1); + + while (buf[*pos] && ' ' == buf[*pos]) + (*pos)++; + + if (buf[*pos]) + return(1); + + if ( ! man_vwarn(m, line, *pos, "trailing spaces")) + return(-1); + + return(1); + } + + /* + * If we're a quoted string (and quoted strings are allowed), + * then parse ahead to the next quote. If none's found, it's an + * error. After, parse to the next word. + */ + + *v = &buf[++(*pos)]; + + while (buf[*pos] && '\"' != buf[*pos]) + (*pos)++; + + if (0 == buf[*pos]) { + if ( ! man_vwarn(m, line, *pos, "unterminated quote")) + return(-1); + return(1); + } + + buf[(*pos)++] = 0; + if (0 == buf[*pos]) + return(1); + + while (buf[*pos] && ' ' == buf[*pos]) + (*pos)++; + + if (buf[*pos]) + return(1); + + if ( ! man_vwarn(m, line, *pos, "trailing spaces")) + return(-1); + return(1); +} diff --git a/usr.bin/mandoc/man_term.c b/usr.bin/mandoc/man_term.c new file mode 100644 index 00000000000..0b1107b8c8f --- /dev/null +++ b/usr.bin/mandoc/man_term.c @@ -0,0 +1,523 @@ +/* $Id: man_term.c,v 1.1 2009/04/06 20:30:40 kristaps Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@openbsd.org> + * + * 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 <assert.h> +#include <err.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "term.h" +#include "man.h" + +#define DECL_ARGS struct termp *p, \ + const struct man_node *n, \ + const struct man_meta *m + +struct termact { + int (*pre)(DECL_ARGS); + void (*post)(DECL_ARGS); +}; + +static int pre_B(DECL_ARGS); +static int pre_BI(DECL_ARGS); +static int pre_BR(DECL_ARGS); +static int pre_I(DECL_ARGS); +static int pre_IB(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_SH(DECL_ARGS); +static int pre_SS(DECL_ARGS); +static int pre_TP(DECL_ARGS); + +static void post_B(DECL_ARGS); +static void post_I(DECL_ARGS); +static void post_SH(DECL_ARGS); +static void post_SS(DECL_ARGS); + +static const struct termact termacts[MAN_MAX] = { + { NULL, NULL }, /* __ */ + { NULL, NULL }, /* TH */ + { pre_SH, post_SH }, /* SH */ + { pre_SS, post_SS }, /* SS */ + { pre_TP, NULL }, /* TP */ + { pre_PP, NULL }, /* LP */ + { pre_PP, NULL }, /* PP */ + { pre_PP, NULL }, /* P */ + { pre_IP, NULL }, /* IP */ + { pre_PP, NULL }, /* HP */ /* FIXME */ + { NULL, NULL }, /* SM */ + { pre_B, post_B }, /* SB */ + { pre_BI, NULL }, /* BI */ + { pre_IB, NULL }, /* IB */ + { pre_BR, 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 */ + { pre_PP, NULL }, /* br */ + { NULL, NULL }, /* na */ + { pre_I, post_I }, /* i */ +}; + +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 *); + + +int +man_run(struct termp *p, const struct man *m) +{ + + print_head(p, man_meta(m)); + p->flags |= TERMP_NOSPACE; + print_body(p, man_node(m), man_meta(m)); + print_foot(p, man_meta(m)); + + return(1); +} + + +/* ARGSUSED */ +static int +pre_I(DECL_ARGS) +{ + + p->flags |= TERMP_UNDER; + return(1); +} + + +/* ARGSUSED */ +static void +post_I(DECL_ARGS) +{ + + p->flags &= ~TERMP_UNDER; +} + + +/* 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->flags |= TERMP_UNDER; + if (i > 0) + p->flags |= TERMP_NOSPACE; + print_node(p, nn, m); + if ( ! (i % 2)) + p->flags &= ~TERMP_UNDER; + } + return(0); +} + + +/* ARGSUSED */ +static int +pre_IB(DECL_ARGS) +{ + const struct man_node *nn; + int i; + + for (i = 0, nn = n->child; nn; nn = nn->next, i++) { + p->flags |= i % 2 ? TERMP_BOLD : TERMP_UNDER; + if (i > 0) + p->flags |= TERMP_NOSPACE; + print_node(p, nn, m); + p->flags &= i % 2 ? ~TERMP_BOLD : ~TERMP_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) + p->flags |= TERMP_BOLD; + if (i > 0) + p->flags |= TERMP_NOSPACE; + print_node(p, nn, m); + if (i % 2) + p->flags &= ~TERMP_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->flags |= TERMP_UNDER; + if (i > 0) + p->flags |= TERMP_NOSPACE; + print_node(p, nn, m); + if ( ! (i % 2)) + p->flags &= ~TERMP_UNDER; + } + return(0); +} + + +/* ARGSUSED */ +static int +pre_BR(DECL_ARGS) +{ + const struct man_node *nn; + int i; + + for (i = 0, nn = n->child; nn; nn = nn->next, i++) { + if ( ! (i % 2)) + p->flags |= TERMP_BOLD; + if (i > 0) + p->flags |= TERMP_NOSPACE; + print_node(p, nn, m); + if ( ! (i % 2)) + p->flags &= ~TERMP_BOLD; + } + 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++) { + p->flags |= i % 2 ? TERMP_UNDER : TERMP_BOLD; + if (i > 0) + p->flags |= TERMP_NOSPACE; + print_node(p, nn, m); + p->flags &= i % 2 ? ~TERMP_UNDER : ~TERMP_BOLD; + } + return(0); +} + + +/* ARGSUSED */ +static int +pre_B(DECL_ARGS) +{ + + p->flags |= TERMP_BOLD; + return(1); +} + + +/* ARGSUSED */ +static void +post_B(DECL_ARGS) +{ + + p->flags &= ~TERMP_BOLD; +} + + +/* ARGSUSED */ +static int +pre_PP(DECL_ARGS) +{ + + term_vspace(p); + p->offset = INDENT; + return(0); +} + + +/* ARGSUSED */ +static int +pre_IP(DECL_ARGS) +{ +#if 0 + const struct man_node *nn; + size_t offs; +#endif + + term_vspace(p); + p->offset = INDENT; + +#if 0 + if (NULL == (nn = n->child)) + return(1); + if (MAN_TEXT != nn->type) + errx(1, "expected text line argument"); + + if (nn->next) { + if (MAN_TEXT != nn->next->type) + errx(1, "expected text line argument"); + offs = (size_t)atoi(nn->next->string); + } else + offs = strlen(nn->string); + + p->offset += offs; +#endif + p->flags |= TERMP_NOSPACE; + return(0); +} + + +/* ARGSUSED */ +static int +pre_TP(DECL_ARGS) +{ + const struct man_node *nn; + size_t offs; + + term_vspace(p); + p->offset = INDENT; + + if (NULL == (nn = n->child)) + return(1); + + if (nn->line == n->line) { + if (MAN_TEXT != nn->type) + errx(1, "expected text line argument"); + offs = (size_t)atoi(nn->string); + nn = nn->next; + } else + offs = INDENT; + + for ( ; nn; nn = nn->next) + print_node(p, nn, m); + + term_flushln(p); + p->flags |= TERMP_NOSPACE; + p->offset += offs; + return(0); +} + + +/* ARGSUSED */ +static int +pre_SS(DECL_ARGS) +{ + + term_vspace(p); + p->flags |= TERMP_BOLD; + return(1); +} + + +/* ARGSUSED */ +static void +post_SS(DECL_ARGS) +{ + + term_flushln(p); + p->flags &= ~TERMP_BOLD; + p->flags |= TERMP_NOSPACE; +} + + +/* ARGSUSED */ +static int +pre_SH(DECL_ARGS) +{ + + term_vspace(p); + p->offset = 0; + p->flags |= TERMP_BOLD; + return(1); +} + + +/* ARGSUSED */ +static void +post_SH(DECL_ARGS) +{ + + term_flushln(p); + p->offset = INDENT; + p->flags &= ~TERMP_BOLD; + p->flags |= TERMP_NOSPACE; +} + + +static void +print_node(DECL_ARGS) +{ + int c, sz; + + c = 1; + + switch (n->type) { + case(MAN_ELEM): + if (termacts[n->tok].pre) + c = (*termacts[n->tok].pre)(p, n, m); + break; + 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; + break; + default: + break; + } + + if (c && n->child) + print_body(p, n->child, m); + + switch (n->type) { + case (MAN_ELEM): + if (termacts[n->tok].post) + (*termacts[n->tok].post)(p, n, m); + break; + default: + break; + } +} + + +static void +print_body(DECL_ARGS) +{ + print_node(p, n, m); + if ( ! n->next) + return; + print_body(p, n->next, m); +} + + +static void +print_foot(struct termp *p, const struct man_meta *meta) +{ + struct tm *tm; + char *buf; + + if (NULL == (buf = malloc(p->rmargin))) + err(1, "malloc"); + + tm = localtime(&meta->date); + + if (NULL == strftime(buf, p->rmargin, "%B %d, %Y", tm)) + err(1, "strftime"); + + 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); + + free(buf); +} + + +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(1, "malloc"); + if (NULL == (title = malloc(p->rmargin))) + err(1, "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)) / 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 00000000000..18f1d12d1a6 --- /dev/null +++ b/usr.bin/mandoc/man_validate.c @@ -0,0 +1,137 @@ +/* $Id: man_validate.c,v 1.1 2009/04/06 20:30:40 kristaps Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@openbsd.org> + * + * 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 <sys/types.h> + +#include <assert.h> +#include <ctype.h> +#include <stdarg.h> +#include <stdlib.h> + +#include "libman.h" + +/* FIXME: validate text. */ + +#define POSTARGS struct man *m, const struct man_node *n + +typedef int (*v_post)(POSTARGS); + +struct man_valid { + v_post *posts; +}; + +static int count(const struct man_node *); +static int check_eq0(POSTARGS); +static int check_ge1(POSTARGS); +static int check_ge2(POSTARGS); +static int check_le1(POSTARGS); +static int check_le2(POSTARGS); +static int check_le5(POSTARGS); + +static v_post posts_le1[] = { check_le1, NULL }; +static v_post posts_le2[] = { check_le2, NULL }; +static v_post posts_ge1[] = { check_ge1, NULL }; +static v_post posts_eq0[] = { check_eq0, NULL }; +static v_post posts_ge2_le5[] = { check_ge2, check_le5, NULL }; + +static const struct man_valid man_valids[MAN_MAX] = { + { NULL }, /* __ */ + { posts_ge2_le5 }, /* TH */ + { posts_ge1 }, /* SH */ + { posts_ge1 }, /* SS */ + { NULL }, /* TP */ + { posts_eq0 }, /* LP */ + { posts_eq0 }, /* PP */ + { posts_eq0 }, /* P */ + { posts_le2 }, /* IP */ + { posts_le1 }, /* HP */ + { NULL }, /* SM */ + { NULL }, /* SB */ + { NULL }, /* BI */ + { NULL }, /* IB */ + { NULL }, /* BR */ + { NULL }, /* RB */ + { NULL }, /* R */ + { NULL }, /* B */ + { NULL }, /* I */ + { NULL }, /* IR */ + { NULL }, /* RI */ + { posts_eq0 }, /* br */ + { posts_eq0 }, /* na */ + { NULL }, /* i */ +}; + + +int +man_valid_post(struct man *m) +{ + v_post *cp; + + if (MAN_VALID & m->last->flags) + return(1); + m->last->flags |= MAN_VALID; + + switch (m->last->type) { + case (MAN_TEXT): + /* FALLTHROUGH */ + case (MAN_ROOT): + return(1); + 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 inline int +count(const struct man_node *n) +{ + int i; + + for (i = 0; n; n = n->next, i++) + /* Loop. */ ; + return(i); +} + + +#define INEQ_DEFINE(x, ineq, name) \ +static int \ +check_##name(POSTARGS) \ +{ \ + int c; \ + if ((c = count(n->child)) ineq (x)) \ + return(1); \ + return(man_verr(m, n->line, n->pos, \ + "expected line arguments %s %d, have %d", \ + #ineq, (x), c)); \ +} + +INEQ_DEFINE(0, ==, eq0) +INEQ_DEFINE(1, >=, ge1) +INEQ_DEFINE(2, >=, ge2) +INEQ_DEFINE(1, <=, le1) +INEQ_DEFINE(2, <=, le2) +INEQ_DEFINE(5, <=, le5) + diff --git a/usr.bin/mandoc/mandoc.1 b/usr.bin/mandoc/mandoc.1 new file mode 100644 index 00000000000..807a83195f7 --- /dev/null +++ b/usr.bin/mandoc/mandoc.1 @@ -0,0 +1,289 @@ +.\" $Id: mandoc.1,v 1.1 2009/04/06 20:30:40 kristaps Exp $ +.\" +.\" Copyright (c) 2009 Kristaps Dzonsons <kristaps@openbsd.org> +.\" +.\" 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: April 6 2009 $ +.Dt mandoc 1 +.Os +.\" SECTION +.Sh NAME +.Nm mandoc +.Nd format and display UNIX manuals +.\" SECTION +.Sh SYNOPSIS +.Nm mandoc +.Op Fl f Ns Ar option... +.Op Fl m Ns Ar format +.Op Fl W Ns Ar err... +.Op Fl T Ns Ar output +.Op Ar infile... +.\" SECTION +.Sh DESCRIPTION +The +.Nm +utility formats +.Ux +manual pages for display. The arguments are as follows: +.Bl -tag -width XXXXXXXXXXXX +.\" ITEM +.It Fl f Ns Ar option... +Override default compiler behaviour. See +.Sx Compiler Options +for details. +.\" ITEM +.It Fl m Ns Ar format +Input format. See +.Sx Input Formats +for available formats. Defaults to +.Fl m Ns Ar andoc . +.\" ITEM +.It Fl T Ns Ar output +Output format. See +.Sx Output Formats +for available formats. Defaults to +.Fl T Ns Ar ascii . +.\" ITEM +.It Fl W Ns Ar err... +Print warning messages. May be set to +.Fl W Ns Ar all +for all warnings, +.Ar compat +for groff/troff-compatibility warnings, or +.Ar syntax +for syntax warnings. If +.Fl W Ns Ar error +is specified, warnings are considered errors and cause utility +termination. Multiple +.Fl W +arguments may be comma-separated, such as +.Fl W Ns Ar error,all . +.\" ITEM +.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 +.\" PARAGRAPH +.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. +.\" PARAGRAPH +.Pp +.Ex -std mandoc +.\" SUB-SECTION +.Ss Punctuation +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 preceeded by whitespace. +.Pp +If the input is +.Xr mdoc 7 , +these rules are also applied to macro arguments when appropriate. +.\" SUB-SECTION +.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. +.\" .Pp +.\" The following escape sequences are recognised, although the per-format +.\" compiler may not allow certain sequences. +.\" .Bl -tag -width Ds -offset XXXX +.\" .It \efX +.\" sets the font mode to X (B, I, R or P, where P resets the font) +.\" .It \eX, \e(XX, \e[XN] +.\" queries the special-character table for a corresponding symbol +.\" .It \e*X, \e*(XX, \e*[XN] +.\" deprecated special-character format +.\" .El +.\" SUB-SECTION +.Ss Output Formats +The +.Nm +utility accepts the following +.Fl T +arguments: +.Bl -tag -width XXXXXXXXXXXX +.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 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. +.\" SUB-SECTION +.Ss Compiler Options +Default compiler behaviour may be overriden with the +.Fl f +flag. +.Bl -tag -width XXXXXXXXXXXXXXXXXXXX +.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 . +.El +.\" PARAGRAPH +.Pp +As with the +.Fl W +flag, multiple +.Fl f +options may be grouped and delimited with a comma. Using +.Fl f Ns Ar ign-scope,no-ign-escape , +for example, will try to ignore scope and not ignore character-escape +errors. +.\" SECTION +.Sh EXAMPLES +To page manuals to the terminal: +.\" PARAGRAPH +.Pp +.D1 % mandoc \-Wall,error \-fstrict mandoc.1 2>&1 | less +.D1 % mandoc mandoc.1 mdoc.3 mdoc.7 | less +.\" 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@openbsd.org . +.\" SECTION +.Sh CAVEATS +The +.Nm +utility in +.Fl T Ns Ar ascii +mode doesn't yet know how to display the following: +.Pp +.Bl -bullet -compact +.It +The \-hang +.Sq \&.Bl +list is not yet supported. +.El +.Pp +Other macros still aren't supported by virtue of nobody complaining +about their absence. Please report any omissions: this is a work in +progress. +.Pp +The following list documents differences between traditional +.Xr nroff 1 +output and +.Nm : +.Pp +.Bl -bullet -compact +.It +A list of display following +.Sq \&.Ss +does not assert a prior vertical break, just as it doesn't with +.Sq \&.Sh . +.It +Special characters don't follow the current font style. +.\" LIST-ITEM +.It +The \-literal and \-unfilled +.Sq \&.Bd +displays types are synonyms, as are \-filled and \-ragged. +.El diff --git a/usr.bin/mandoc/mandoc_char.7 b/usr.bin/mandoc/mandoc_char.7 new file mode 100644 index 00000000000..29e93beaee0 --- /dev/null +++ b/usr.bin/mandoc/mandoc_char.7 @@ -0,0 +1,469 @@ +.\" $Id: mandoc_char.7,v 1.1 2009/04/06 20:30:40 kristaps Exp $ +.\" +.\" Copyright (c) 2009 Kristaps Dzonsons <kristaps@openbsd.org> +.\" +.\" 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: April 6 2009 $ +.Dt mandoc_char 7 +.Os +.\" SECTION +.Sh NAME +.Nm mandoc_char +.Nd mandoc special characters +.\" SECTION +.Sh DESCRIPTION +This documents the special characters accepted by +.Xr mandoc 1 +to format +.Xr mdoc 7 +and +.Xr man 7 +documents. Specific output devices of +.Xr mandoc 1 , +dictated by the +.Fl T Ns Ar output +argument, will properly render these sequences. +.Pp +Both +.Xr mdoc 7 +and +.Xr man 7 +encode special characters with slightly different semantics; consult the +respective manuals for these escapes. +.Pp +Grammatic: +.Bl -tag -width 12n -offset "XXXX" -compact +.It \e(em +.Pq em-dash +.It \e(en +.Pq en-dash +.It \e- +.Pq hyphen +.It \e\e +.Pq back-slash +.It \e' +.Pq apostrophe +.It \e` +.Pq back-tick +.It \e +.Pq space +.It \e. +.Pq period +.It \e(r! +.Pq upside-down exclamation +.It \e(r? +.Pq upside-down question +.El +.\" PARAGRAPH +.Pp +Enclosures: +.Bl -tag -width 12n -offset "XXXX" -compact +.It \e(lh +.Pq left hand +.It \e(rh +.Pq right hand +.It \e(Fo +.Pq left guillemet +.It \e(Fc +.Pq right guillemet +.It \e(fo +.Pq left guilsing +.It \e(fc +.Pq right guilsing +.It \e(rC +.Pq right brace +.It \e(lC +.Pq left brace +.It \e(ra +.Pq right angle +.It \e(la +.Pq left angle +.It \e(rB +.Pq right bracket +.It \e(lB +.Pq left bracket +.It \eq +.Pq double-quote +.It \e(lq +.Pq left double-quote +.It \e(Lq +.Pq left double-quote, deprecated +.It \e(rq +.Pq right double-quote +.It \e(Rq +.Pq right double-quote, deprecated +.It \e(oq +.Pq left single-quote +.It \e(aq +.Pq right single-quote +.It \e(Bq +.Pq right low double-quote +.It \e(bq +.Pq right low single-quote +.El +.\" PARAGRAPH +.Pp +Indicatives: +.Bl -tag -width 12n -offset "XXXX" -compact +.It \e(<- +.Pq left arrow +.It \e(-> +.Pq right arrow +.It \e(ua +.Pq up arrow +.It \e(da +.Pq down arrow +.It \e(<> +.Pq left-right arrow +.It \e(lA +.Pq left double-arrow +.It \e(rA +.Pq right double-arrow +.It \e(uA +.Pq up double-arrow +.It \e(dA +.Pq down double-arrow +.It \e(hA +.Pq left-right double-arrow +.El +.\" PARAGRAPH +.Pp +Mathematical: +.Bl -tag -width 12n -offset "XXXX" -compact +.It \e(es +.Pq empty set +.It \e(ca +.Pq intersection +.It \e(cu +.Pq union +.It \e(gr +.Pq gradient +.It \e(pd +.Pq partial differential +.It \e(ap +.Pq similarity +.It \e(=) +.Pq proper superset +.It \e((= +.Pq proper subset +.It \e(eq +.Pq equals +.It \e(di +.Pq division +.It \e(mu +.Pq multiplication +.It \e(pl +.Pq addition +.It \e(nm +.Pq not element +.It \e(mo +.Pq element +.It \e(Im +.Pq imaginary +.It \e(Re +.Pq real +.It \e(Ah +.Pq aleph +.It \e(te +.Pq existential quantifier +.It \e(fa +.Pq universal quantifier +.It \e(AN +.Pq logical AND +.It \e(OR +.Pq logical OR +.It \e(no +.Pq logical NOT +.It \e(st +.Pq such that +.It \e(tf +.Pq therefore +.It \e(~~ +.Pq approximate +.It \e(~= +.Pq approximately equals +.It \e(=~ +.Pq congruent +.It \e(Gt +.Pq greater-than, deprecated +.It \e(Lt +.Pq less-than, deprecated +.It \e(<= +.Pq less-than-equal +.It \e(Le +.Pq less-than-equal, deprecated +.It \e(>= +.Pq greater-than-equal +.It \e(Ge +.Pq greater-than-equal +.It \e(== +.Pq equal +.It \e(!= +.Pq not equal +.It \e(Ne +.Pq not equal, deprecated +.It \e(if +.Pq infinity +.It \e(If +.Pq infinity, deprecated +.It \e(na +.Pq NaN , an extension +.It \e(Na +.Pq NaN, deprecated +.It \e(+- +.Pq plus-minus +.It \e(Pm +.Pq plus-minus, deprecated +.It \e(** +.Pq asterisk +.El +.\" PARAGRAPH +.Pp +Ligatures: +.Bl -tag -width 12n -offset "XXXX" -compact +.It \e(ss +.Pq German eszett +.It \e(AE +.Pq upper-case AE +.It \e(ae +.Pq lower-case AE +.It \e(OE +.Pq upper-case OE +.It \e(oe +.Pq lower-case OE +.It \e(ff +.Pq ff ligature +.It \e(fi +.Pq fi ligature +.It \e(fl +.Pq fl ligature +.It \e(Fi +.Pq ffi ligature +.It \e(Fl +.Pq ffl ligature +.El +.\" PARAGRAPH +.Pp +Diacritics and letters: +.Bl -tag -width 12n -offset "XXXX" -compact +.It \e(ga +.Pq grave accent +.It \e(aa +.Pq accute accent +.It \e(a" +.Pq umlaut accent +.It \e(ad +.Pq dieresis accent +.It \e(a~ +.Pq tilde accent +.It \e(a^ +.Pq circumflex accent +.It \e(ac +.Pq cedilla accent +.It \e(ad +.Pq dieresis accent +.It \e(ah +.Pq caron accent +.It \e(ao +.Pq ring accent +.It \e(ho +.Pq hook accent +.It \e(ab +.Pq breve accent +.It \e(a- +.Pq macron accent +.It \e(-D +.Pq upper-case eth +.It \e(Sd +.Pq lower-case eth +.It \e(TP +.Pq upper-case thorn +.It \e(Tp +.Pq lower-case thorn +.It \e('A +.Pq upper-case acute A +.It \e('E +.Pq upper-case acute E +.It \e('I +.Pq upper-case acute I +.It \e('O +.Pq upper-case acute O +.It \e('U +.Pq upper-case acute U +.It \e('a +.Pq lower-case acute a +.It \e('e +.Pq lower-case acute e +.It \e('i +.Pq lower-case acute i +.It \e('o +.Pq lower-case acute o +.It \e('u +.Pq lower-case acute u +.It \e(`A +.Pq upper-case grave A +.It \e(`E +.Pq upper-case grave E +.It \e(`I +.Pq upper-case grave I +.It \e(`O +.Pq upper-case grave O +.It \e(`U +.Pq upper-case grave U +.It \e(`a +.Pq lower-case grave a +.It \e(`e +.Pq lower-case grave e +.It \e(`i +.Pq lower-case grave i +.It \e(`o +.Pq lower-case grave o +.It \e(`u +.Pq lower-case grave u +.It \e(~A +.Pq upper-case tilde A +.It \e(~N +.Pq upper-case tilde N +.It \e(~O +.Pq upper-case tilde O +.It \e(~a +.Pq lower-case tilde a +.It \e(~n +.Pq lower-case tilde n +.It \e(~o +.Pq lower-case tilde o +.It \e(:A +.Pq upper-case dieresis A +.It \e(:E +.Pq upper-case dieresis E +.It \e(:I +.Pq upper-case dieresis I +.It \e(:O +.Pq upper-case dieresis O +.It \e(:U +.Pq upper-case dieresis U +.It \e(:a +.Pq lower-case dieresis a +.It \e(:e +.Pq lower-case dieresis e +.It \e(:i +.Pq lower-case dieresis i +.It \e(:o +.Pq lower-case dieresis o +.It \e(:u +.Pq lower-case dieresis u +.It \e(:y +.Pq lower-case dieresis y +.It \e(^A +.Pq upper-case circumflex A +.It \e(^E +.Pq upper-case circumflex E +.It \e(^I +.Pq upper-case circumflex I +.It \e(^O +.Pq upper-case circumflex O +.It \e(^U +.Pq upper-case circumflex U +.It \e(^a +.Pq lower-case circumflex a +.It \e(^e +.Pq lower-case circumflex e +.It \e(^i +.Pq lower-case circumflex i +.It \e(^o +.Pq lower-case circumflex o +.It \e(^u +.Pq lower-case circumflex u +.It \e(,C +.Pq upper-case cedilla C +.It \e(,c +.Pq lower-case cedilla c +.It \e(/L +.Pq upper-case stroke L +.It \e(/l +.Pq lower-case stroke l +.It \e(/O +.Pq upper-case stroke O +.It \e(/o +.Pq lower-case stroke o +.It \e(oA +.Pq upper-case ring A +.It \e(oa +.Pq lower-case ring a +.El +.\" PARAGRAPH +.Pp +Monetary: +.Bl -tag -width 12n -offset "XXXX" -compact +.It \e(Cs +.Pq Scandinavian +.It \e(Do +.Pq dollar +.It \e(Po +.Pq pound +.It \e(Ye +.Pq yen +.It \e(Fn +.Pq florin +.It \e(ct +.Pq cent +.El +.\" PARAGRAPH +.Pp +Special symbols: +.Bl -tag -width 12n -offset "XXXX" -compact +.It \e0 +.Pq white-space +.It \e(de +.Pq degree +.It \e(ps +.Pq paragraph +.It \e(sc +.Pq section +.It \e(dg +.Pq dagger +.It \e(dd +.Pq double dagger +.It \e(ci +.Pq circle +.It \e(ba +.Pq bar +.It \e(bb +.Pq broken bar +.It \e(Ba +.Pq bar, deprecated +.It \e(co +.Pq copyright +.It \e(rg +.Pq registered +.It \e(tm +.Pq trademarked +.It \e& +.Pq non-breaking space +.It \ee +.Pq escape +.It \e(Am +.Pq ampersand, deprecated +.El +.\" SECTION +.Sh SEE ALSO +.Xr mandoc 1 +.\" SECTION +.Sh AUTHORS +The +.Nm +utility was written by +.An Kristaps Dzonsons Aq kristaps@openbsd.org . diff --git a/usr.bin/mandoc/manuals.7 b/usr.bin/mandoc/manuals.7 new file mode 100644 index 00000000000..6ebfd1bc297 --- /dev/null +++ b/usr.bin/mandoc/manuals.7 @@ -0,0 +1,257 @@ +.\" $Id: manuals.7,v 1.1 2009/04/06 20:30:40 kristaps Exp $ +.\" +.\" Copyright (c) 2009 Kristaps Dzonsons <kristaps@openbsd.org> +.\" +.\" 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: April 6 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 COMPOSITION +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 Input Language +Manuals should +.Em always +be written in the +.Xr mdoc 7 +formatting language. +.Pp +There exist other documentation-specific languages, such as the +historical +.Xr man 7 +package of +.Xr roff 7 ; +newer languages such as DocBook or texinfo; or even ad-hoc conventions +such as README files. +.Em Avoid these formats . +.Pp +There are two canonical references for writing mdoc. Read them. +.Pp +.\" LIST +.Bl -tag -width XXXXXXXXXXXXXXXX -offset indent -compact +.It Xr mdoc 7 +formal language reference +.It Xr mdoc.samples 7 +macro reference +.El +.Pp +Open the template you've copied into +.Pa myname.mysection +and begin editing. +.\" SUBSECTION +.Ss Development Tools +While writing, make sure that your manual is correctly structured: +.Pp +.Dl % mandoc \-Tlint \-Wall name.1 +.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: April 6 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 BEST PRACTICES +The +.Xr mdoc 7 +and +.Xr mdoc.samples 7 +files are indispensable in guiding composition. In this section, we +introduce some +.Ux +manual best practices: +.\" 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 00000000000..bb4ae4ea2de --- /dev/null +++ b/usr.bin/mandoc/mdoc.3 @@ -0,0 +1,346 @@ +.\" $Id: mdoc.3,v 1.1 2009/04/06 20:30:40 kristaps Exp $ +.\" +.\" Copyright (c) 2009 Kristaps Dzonsons <kristaps@openbsd.org> +.\" +.\" 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: April 6 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 +.Fd #include <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 "struct mdoc *mdoc" +.Ft "const struct mdoc_meta *" +.Fn mdoc_meta "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; +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@openbsd.org . +.\" 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 <string> ; +instead, a value of +.Li 10n +is provided. +.\" LIST-ITEM +.It +Columns widths in +.Sq \&.Bl \-column +should default to width +.Qq <stringx> +if not included. +.\" LIST-ITEM +.It +List-width suffix +.Qq m +isn't handled. +.\" LIST-ITEM +.It +Contents of the SYNOPSIS section aren't checked. +.El diff --git a/usr.bin/mandoc/mdoc.7 b/usr.bin/mandoc/mdoc.7 new file mode 100644 index 00000000000..bf66b2072a7 --- /dev/null +++ b/usr.bin/mandoc/mdoc.7 @@ -0,0 +1,604 @@ +.\" $Id: mdoc.7,v 1.1 2009/04/06 20:30:40 kristaps Exp $ +.\" +.\" Copyright (c) 2009 Kristaps Dzonsons <kristaps@openbsd.org> +.\" +.\" 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: April 6 2009 $ +.Dt mdoc 7 +.Os +.\" SECTION +.Sh NAME +.Nm mdoc +.Nd mdoc language reference +.\" SECTION +.Sh DESCRIPTION +The +.Nm mdoc +language is used to format +.Bx +.Ux +manuals. In this reference document, we describe the syntax, ontology +and structure of the +.Nm +language. +.\" PARAGRAPH +.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 XXX +\&.Sh Macro lines change control state. +Other lines are interpreted within the current state. +.Ed +.\" SECTION +.Sh INPUT ENCODING +.Nm +documents may contain only graphable 7-bit ASCII characters, the space +character +.Sq \ , +and, in certain circumstances, the tab character +.Sq \et . +All manuals must have +.Sq \en +line termination. +.Pp +The only time a blank line is acceptable is within +the context of +.Sq \&.Bd \-literal +or +.Sq \&.Bd \-unfilled . +.Pp +Tab characters +.Pq \et +are only acceptable when delimiting +.Sq \&.Bl \-column +and in +.Sq \&.Bd \-literal +or +.Sq \&.Bd \-unfilled +contexts. +.\" SUB-SECTION +.Ss Reserved Characters +Within a macro line, the following characters are reserved: +.Bl -tag -width 12n -offset XXXX -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 +.El +.\" PARAGRAPH +.Pp +Use of reserved characters is described in +.Sx Closure . +For general non-reserved use, characters must either be escaped with a +non-breaking space +.Pq Sq \e& +or, if applicable, an appropriate escape-sequence used. +.\" SUB-SECTION +.Ss Special Characters +Special character 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. +.Pp +Characters may alternatively be escaped by a slash-asterisk, +.Sq \e* , +with the same combinations as described above. This form is deprecated. +.\" SECTION +.Sh STRUCTURE +Macros are classified in an ontology described by their scope rules. +Some macros are allowed to deviate from their classifications to +preserve backward-compatibility with old macro combinations still found +in the manual corpus. These are specifically noted on a per-macro +basis. +.\" SUB-SECTION +.Ss Scope +.Bl -inset +.\" LIST-ITEM +.It Em Block +macros enclose other block macros, in-line macros or text, and +may span multiple lines. +.Bl -inset -offset XXXX +.\" LIST-ITEM +.It Em Full-block +macros always span multiple lines. They consist of zero or +more +.Qq heads , +subsequent macros or text on the same line following invocation; an +optional +.Qq body , +which spans subsequent lines of text or macros; and an optional +.Qq tail , +macros or text on the same line following closure. +.\" LIST-ITEM +.It Em Partial-block +macros may span multiple lines. They consists of a optional +.Qq head , +text immediately following invocation; always a +.Qq body , +text or macros following the head on the same and subsequent lines; and +optionally a +.Qq tail , +text immediately following closure. +.\" LIST-ITEM +.It Em In-line +macros may only enclose text and span at most a single line. +.El +.El +.\" SUB-SECTION +.Ss Closure +Closure of a macro's scope depends first on its classification, then +on whether it's parsable. In this table, +.Sq BFE +refers to block full-explicit and so on. +.\" PARAGRAPH +.Pp +.Bl -tag -width 12n -offset XXXX -compact +.It BPE , BFE +corresponding explicit closure macro +.It BFI +end-of-file or a corresponding implicit closure macro +.It BPI +end-of-line (body may be closed by >0 space-separated +.Sx Reserved Characters , +although block scope will still be open) +.It INL +end-of-line +.El +.\" PARAGRAPH +.Pp +If a macro (block or in-line) is parsable, it may also be closed out by +one of the following scenarios (unless specifically noted otherwise): +.\" PARAGRAPH +.Pp +.Bl -dash -offset XXXX -compact +.It +a sequence of >0 space-separated +.Sx Reserved Characters , +.It +another macro, +.It +end-of-line, or +.It +completion of a set number of arguments. +.El +.\" PARAGRAPH +.Pp +If >0 space-separated +.Sx Reserved Characters +are followed by non-reserved characters, the behaviour differs per +macro. In general, scope of the macro is closed and re-opened: +subsequent tokens are interpreted as if the scope had just been opened. +In other circumstances, scope is simply closed out. +.\" SECTION +.Sh SYNTAX +Macros are two or three characters in length. The syntax of macro +invocation depends on its classification. +.Qq \-arg +refers to the macro arguments (which may contain zero or more values). +In these illustrations, +.Sq \&.Yo +opens the scope of a macro, and if specified, +.Sq \&.Yc +closes it out (closure may be implicit at end-of-line or end-of-file). +.\" PARAGRAPH +.Pp +Block full-explicit (may contain head, body, tail). +.Bd -literal -offset XXXX +\&.Yo \(lB\-arg \(lBval...\(rB\(rB \(lBhead...\(rB +\(lBbody...\(rB +\&.Yc \(lBtail...\(rB +.Ed +.\" PARAGRAPH +.Pp +Block full-implicit (may contain zero or more heads, body, no tail). +.Bd -literal -offset XXXX +\&.Yo \(lB\-arg \(lBval...\(rB\(rB \(lBhead... \(lBTa head...\(rB\(rB +\(lBbody...\(rB +\&.Yc +.Ed +.\" PARAGRAPH +.Pp +Block partial-explicit (may contain head, multi-line body, tail). +.Bd -literal -offset XXXX +\&.Yo \(lB\-arg \(lBval...\(rB\(rB \(lBhead...\(rB +\(lBbody...\(rB +\&.Yc \(lBtail...\(rB + +\&.Yo \(lB\-arg \(lBval...\(rB\(rB \(lBhead...\(rB \ +\(lBbody...\(rB \&Yc \(lBtail...\(rB +.Ed +.\" PARAGRAPH +.Pp +Block partial-implicit (no head, body, no tail). Note that the body +section may be followed by zero or more +.Sx Reserved Words . +These are in the block scope, but not in the body scope. +.Bd -literal -offset XXXX +\&.Yo \(lB\-arg \(lBval...\(rB\(rB \(lBbody...\(rB \(lBreserved...\(rB +.Ed +.\" PARAGRAPH +.Pp +In-lines have \(>=0 scoped arguments. +.Bd -literal -offset XXX +\&.Yy \(lB\-arg \(lBval...\(rB\(rB \(lBargs...\(rB + +\&.Yy \(lB\-arg \(lBval...\(rB\(rB arg0 arg1 argN +.Ed +.\" +.Sh MACROS +This section contains a complete list of all +.Nm +macros, arranged ontologically. A +.Qq callable +macro is invoked subsequent to the initial macro-line macro. A +.Qq parsable +macro may be followed by further (ostensibly callable) macros. +.\" SUB-SECTION +.Ss Block full-implicit +The head of these macros follows invocation; the body is the content of +subsequent lines prior to closure. None of these macros have tails; +some +.Po +.Sq \&.It \-bullet , +.Sq \-hyphen , +.Sq \-dash , +.Sq \-enum , +.Sq \-item +.Pc +don't have heads. +.Pp +.Bl -column "MacroX" "CallableX" "ParsableX" "Closing" -compact -offset XXXX +.It Em Macro Ta Em Callable Ta Em Parsable Ta Em Closing +.It \&.Sh Ta \&No Ta \&No Ta \&.Sh +.It \&.Ss Ta \&No Ta \&No Ta \&.Sh, \&.Ss +.It \&.It Ta \&No Ta Yes Ta \&.It, \&.El +.El +.\" SUB-SECTION +.Ss Block full-explicit +None of these macros are callable or parsed. The last column indicates +the explicit scope rules. All contains bodies, some may contain heads +.Pq So \&Bf Sc . +.Pp +.Bl -column "MacroX" "CallableX" "ParsableX" "closed by XXX" -compact -offset XXXX +.It Em Macro Ta Em Callable Ta Em Parsable Ta Em Scope +.It \&.Bd Ta \&No Ta \&No Ta closed by \&.Ed +.It \&.Ed Ta \&No Ta \&No Ta opened by \&.Bd +.It \&.Bl Ta \&No Ta \&No Ta closed by \&.El +.It \&.El Ta \&No Ta \&No Ta opened by \&.Bl +.It \&.Bf Ta \&No Ta \&No Ta closed by \&.Ef +.It \&.Ef Ta \&No Ta \&No Ta opened by \&.Bf +.It \&.Bk Ta \&No Ta \&No Ta closed by \&.Ek +.It \&.Ek Ta \&No Ta \&No Ta opened by \&.Bk +.El +.\" SUB-SECTION +.Ss Block partial-implicit +All of these are callable and parsed for further macros. Their scopes +close at the invocation's end-of-line. +.Pp +.Bl -column "MacroX" "CallableX" "ParsableX" -compact -offset XXXX +.It Em Macro Ta Em Callable Ta Em Parsable +.It \&.Aq Ta Yes Ta Yes +.It \&.Op Ta Yes Ta Yes +.It \&.Bq Ta Yes Ta Yes +.It \&.Dq Ta Yes Ta Yes +.It \&.Pq Ta Yes Ta Yes +.It \&.Qq Ta Yes Ta Yes +.It \&.Sq Ta Yes Ta Yes +.It \&.Brq Ta Yes Ta Yes +.It \&.D1 Ta \&No Ta \&Yes +.It \&.Dl Ta \&No Ta Yes +.It \&.Ql Ta Yes Ta Yes +.El +.\" PARAGRAPH +.Pp +The +.Sq \&.Op +may be broken by +.Sq \&.Oc +as in the following example: +.Bd -literal -offset XXXX +\&.Oo +\&.Op Fl a Oc +.Ed +.Pp +In the above example, the scope of +.Sq \&.Op +is technically broken by +.Sq \&.Oc , +however, due to the overwhelming existence of this sequence, it's +allowed. +.\" SUB-SECTION +.Ss Block partial-explicit +Each of these contains at least a body and, in limited circumstances, a +head +.Pq So \&.Fo Sc , So \&.Eo Sc +and/or tail +.Pq So \&.Ec Sc . +.Pp +.Bl -column "MacroX" "CallableX" "ParsableX" "closed by XXXX" -compact -offset XXXX +.It Em Macro Ta Em Callable Ta Em Parsable Ta Em Scope +.It \&.Ao Ta Yes Ta Yes Ta closed by \&.Ac +.It \&.Ac Ta Yes Ta Yes Ta opened by \&.Ao +.It \&.Bc Ta Yes Ta Yes Ta closed by \&.Bo +.It \&.Bo Ta Yes Ta Yes Ta opened by \&.Bc +.It \&.Pc Ta Yes Ta Yes Ta closed by \&.Po +.It \&.Po Ta Yes Ta Yes Ta opened by \&.Pc +.It \&.Do Ta Yes Ta Yes Ta closed by \&.Dc +.It \&.Dc Ta Yes Ta Yes Ta opened by \&.Do +.It \&.Xo Ta Yes Ta Yes Ta closed by \&.Xc +.It \&.Xc Ta Yes Ta Yes Ta opened by \&.Xo +.It \&.Bro Ta Yes Ta Yes Ta closed by \&.Brc +.It \&.Brc Ta Yes Ta Yes Ta opened by \&.Bro +.It \&.Oc Ta Yes Ta Yes Ta closed by \&.Oo +.It \&.Oo Ta Yes Ta Yes Ta opened by \&.Oc +.It \&.So Ta Yes Ta Yes Ta closed by \&.Sc +.It \&.Sc Ta Yes Ta Yes Ta opened by \&.So +.It \&.Fc Ta Yes Ta Yes Ta opened by \&.Fo +.It \&.Fo Ta \&No Ta \&No Ta closed by \&.Fc +.It \&.Ec Ta Yes Ta Yes Ta opened by \&.Eo +.It \&.Eo Ta Yes Ta Yes Ta closed by \&.Ec +.It \&.Qc Ta Yes Ta Yes Ta opened by \&.Oo +.It \&.Qo Ta Yes Ta Yes Ta closed by \&.Oc +.It \&.Re Ta \&No Ta \&No Ta opened by \&.Rs +.It \&.Rs Ta \&No Ta \&No Ta closed by \&.Re +.El +.\" SUB-SECTION +.Ss In-line +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. +.Pp +.Bl -column "MacroX" "CallableX" "ParsableX" "Arguments" -compact -offset XXXX +.It Em Macro Ta Em Callable Ta Em Parsable Ta Em Arguments +.It \&.Dd Ta \&No Ta \&No Ta >0 +.It \&.Dt Ta \&No Ta \&No Ta n +.It \&.Os Ta \&No Ta \&No Ta n +.It \&.Pp Ta \&No Ta \&No Ta 0 +.It \&.Ad Ta Yes Ta Yes Ta n +.It \&.An Ta \&No Ta Yes Ta n +.It \&.Ar Ta Yes Ta Yes Ta n +.It \&.Cd Ta Yes Ta \&No Ta >0 +.It \&.Cm Ta Yes Ta Yes Ta n +.It \&.Dv Ta Yes Ta Yes Ta n +.It \&.Er Ta Yes Ta Yes Ta >0 +.It \&.Ev Ta Yes Ta Yes Ta n +.It \&.Ex Ta \&No Ta \&No Ta 0 +.It \&.Fa Ta Yes Ta Yes Ta n +.It \&.Fd Ta \&No Ta \&No Ta >0 +.It \&.Fl Ta Yes Ta Yes Ta n +.It \&.Fn Ta Yes Ta Yes Ta >0 +.It \&.Ft Ta \&No Ta Yes Ta n +.It \&.Ic Ta Yes Ta Yes Ta >0 +.It \&.In Ta \&No Ta \&No Ta n +.It \&.Li Ta Yes Ta Yes Ta n +.It \&.Nd Ta \&No Ta \&No Ta n +.It \&.Nm Ta Yes Ta Yes Ta n +.It \&.Ot Ta \&No Ta \&No Ta n +.It \&.Pa Ta Yes Ta Yes Ta n +.It \&.Rv Ta \&No Ta \&No Ta 0 +.It \&.St Ta \&No Ta Yes Ta 1 +.It \&.Va Ta Yes Ta Yes Ta n +.It \&.Vt Ta Yes Ta Yes Ta >0 +.It \&.Xr Ta Yes Ta Yes Ta >0, <3 +.It \&.%A Ta \&No Ta \&No Ta >0 +.It \&.%B Ta \&No Ta \&No Ta >0 +.It \&.%C Ta \&No Ta \&No Ta >0 +.It \&.%D Ta \&No Ta \&No Ta >0 +.It \&.%I Ta \&No Ta \&No Ta >0 +.It \&.%J Ta \&No Ta \&No Ta >0 +.It \&.%N Ta \&No Ta \&No Ta >0 +.It \&.%O Ta \&No Ta \&No Ta >0 +.It \&.%P Ta \&No Ta \&No Ta >0 +.It \&.%R Ta \&No Ta \&No Ta >0 +.It \&.%T Ta \&No Ta \&No Ta >0 +.It \&.%V Ta \&No Ta \&No Ta >0 +.It \&.At Ta Yes Ta Yes Ta 1 +.It \&.Bsx Ta Yes Ta Yes Ta n +.It \&.Bx Ta Yes Ta Yes Ta n +.It \&.Db Ta \&No Ta \&No Ta 1 +.It \&.Em Ta Yes Ta Yes Ta >0 +.It \&.Fx Ta Yes Ta Yes Ta n +.It \&.Ms Ta \&No Ta Yes Ta >0 +.It \&.No Ta Yes Ta Yes Ta 0 +.It \&.Ns Ta Yes Ta Yes Ta 0 +.It \&.Nx Ta Yes Ta Yes Ta n +.It \&.Ox Ta Yes Ta Yes Ta n +.It \&.Pf Ta \&No Ta Yes Ta 1 +.It \&.Sm Ta \&No Ta \&No Ta 1 +.It \&.Sx Ta Yes Ta Yes Ta >0 +.It \&.Sy Ta Yes Ta Yes Ta >0 +.It \&.Tn Ta Yes Ta Yes Ta >0 +.It \&.Ux Ta Yes Ta Yes Ta n +.It \&.Dx Ta Yes Ta Yes Ta n +.It \&.Bt Ta \&No Ta \&No Ta 0 +.It \&.Hf Ta \&No Ta \&No Ta n +.It \&.Fr Ta \&No Ta \&No Ta n +.It \&.Ud Ta \&No Ta \&No Ta 0 +.It \&.Lb Ta \&No Ta \&No Ta 1 +.It \&.Ap Ta Yes Ta Yes Ta 0 +.It \&.Lp Ta \&No Ta \&No Ta 0 +.It \&.Lk Ta \&No Ta Yes Ta >0 +.It \&.Mt Ta \&No Ta Yes Ta >0 +.It \&.Es Ta \&No Ta \&No Ta 0 +.It \&.En Ta \&No Ta \&No Ta 0 +.El +.Pp +The +.Sq \&.Ot , +.Sq \&.Fr , +.Sq \&.Es +and +.Sq \&.En , +macros are obsolete. +.\" SECTION +.Sh COMPATIBILITY +The mdoc language was traditionally a +.Qq roff +macro package; most existing manuals were written with mdoc syntax +dictated by system-dependent roff installations. This section documents +compatibility with these systems. +.Pp +.Bl -dash -compact +.\" LIST-ITEM +.It +.Sq \&.Fo +and +.Sq \&.St +historically weren't always callable. Both are now correctly callable. +.\" LIST-ITEM +.It +.Sq \&.It \-nested +is assumed for all lists: any list may be nested and +.Sq \-enum +lists will restart the sequence only for the sub-list. +.\" LIST-ITEM +.It +.Sq \&.It \-column +syntax where column widths may be preceeded by other arguments (instead +of proceeded) is not supported. +.\" LIST-ITEM +.It +The +.Sq \&.At +macro only accepts a single parameter. +.\" LIST-ITEM +.It +The system-name macros ( +.Ns Sq \&.At , +.Sq \&.Bsx , +.Sq \&.Bx , +.Sq \&.Fx , +.Sq \&.Nx , +.Sq \&.Ox , +and +.Sq \&.Ux ) +are callable. +.\" LIST-ITEM +.It +Some manuals use +.Sq \&.Li +incorrectly by following it with a reserved character and expecting the +delimiter to render. This is not supported. +.\" LIST-ITEM +.It +.Sq \&.Cd +is callable. +.El +.\" SECTION +.Sh SEE ALSO +.Xr mandoc 1 , +.Xr mandoc_char 7 +.\" SECTION +.Sh AUTHORS +The +.Nm +utility was written by +.An Kristaps Dzonsons Aq kristaps@openbsd.org . +.\" SECTION +.Sh CAVEATS +There are several ambiguous parts of mdoc. +.Pp +.Bl -dash -compact +.\" LIST-ITEM +.It +.Sq \&.Fa +should be +.Sq \&.Va +as function arguments are variables. +.\" LIST-ITEM +.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 . +.\" LIST-ITEM +.It +.Sq \&.Va +should formalise that only one or two arguments are acceptable: a +variable name and optional, preceeding type. +.\" LIST-ITEM +.It +.Sq \&.Fd +is ambiguous. It's commonly used to indicate an include file in the +synopsis section. +.Sq \&.In +should be used, instead. +.\" LIST-ITEM +.It +Only the +.Sq \-literal +argument to +.Sq \&.Bd +makes sense. The remaining ones should be removed. +.\" LIST-ITEM +.It +The +.Sq \&.Xo +and +.Sq \&.Xc +macros should be deprecated. +.\" LIST-ITEM +.It +The +.Sq \&.Dt +macro lacks clarity. It should be absolutely clear which title will +render when formatting the manual page. +.\" LIST-ITEM +.It +A +.Sq \&.Lx +should be provided for Linux (\(`a la +.Sq \&.Ox , +.Sq \&.Nx +etc.). +.\" LIST-ITEM +.It +There's no way to refer to references in +.Sq \&.Rs/.Re +blocks. +.El diff --git a/usr.bin/mandoc/mdoc.c b/usr.bin/mandoc/mdoc.c new file mode 100644 index 00000000000..ca4a42f74dc --- /dev/null +++ b/usr.bin/mandoc/mdoc.c @@ -0,0 +1,651 @@ +/* $Id: mdoc.c,v 1.1 2009/04/06 20:30:40 kristaps Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@openbsd.org> + * + * 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 <assert.h> +#include <ctype.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "libmdoc.h" + +enum merr { + ENOCALL, + EBODYPROL, + EPROLBODY, + ESPACE, + ETEXTPROL, + ENOBLANK, + EMALLOC +}; + +const char *const __mdoc_macronames[MDOC_MAX] = { + "\\\"", "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", "Ap", + "Lp", "Lk", "Mt", "Brq", + /* LINTED */ + "Bro", "Brc", "\%C", "Es", + /* LINTED */ + "En", "Dx", "\%Q" + }; + +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" + }; + +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 perr(struct mdoc *, int, int, enum merr); + +#define verr(m, t) perr((m), (m)->last->line, (m)->last->pos, (t)) + +/* + * Get the first (root) node of the parse tree. + */ +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); +} + + +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); +} + + +static int +mdoc_alloc1(struct mdoc *mdoc) +{ + + bzero(&mdoc->meta, sizeof(struct mdoc_meta)); + mdoc->flags = 0; + mdoc->lastnamed = mdoc->lastsec = 0; + 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 all resources contributed by a parse: the node tree, + * meta-data and so on. Then reallocate the root node for another + * parse. + */ +int +mdoc_reset(struct mdoc *mdoc) +{ + + mdoc_free1(mdoc); + return(mdoc_alloc1(mdoc)); +} + + +/* + * Completely free up all resources. + */ +void +mdoc_free(struct mdoc *mdoc) +{ + + mdoc_free1(mdoc); + if (mdoc->htab) + mdoc_hash_free(mdoc->htab); + free(mdoc); +} + + +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)); + + p->data = data; + p->pflags = pflags; + + if (NULL == (p->htab = mdoc_hash_alloc())) { + free(p); + return(NULL); + } else 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 or text parser. + */ +int +mdoc_parseln(struct mdoc *m, int ln, char *buf) +{ + + /* If in error-mode, then we parse no more. */ + + if (MDOC_HALT & m->flags) + return(0); + + return('.' == *buf ? parsemacro(m, ln, buf) : + parsetext(m, ln, buf)); +} + + +void +mdoc_vmsg(struct mdoc *mdoc, int ln, int pos, const char *fmt, ...) +{ + char buf[256]; + va_list ap; + + if (NULL == mdoc->cb.mdoc_msg) + return; + + va_start(ap, fmt); + (void)vsnprintf(buf, sizeof(buf) - 1, fmt, ap); + va_end(ap); + (*mdoc->cb.mdoc_msg)(mdoc->data, ln, pos, 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, + enum mdoc_warn type, 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, type, buf)); +} + + +int +mdoc_macro(struct mdoc *m, int tok, + int ln, int pp, int *pos, char *buf) +{ + + /* FIXME - these should happen during validation. */ + + if (MDOC_PROLOGUE & mdoc_macros[tok].flags && + SEC_PROLOGUE != m->lastnamed) + return(perr(m, ln, pp, EPROLBODY)); + + if ( ! (MDOC_PROLOGUE & mdoc_macros[tok].flags) && + SEC_PROLOGUE == m->lastnamed) + return(perr(m, ln, pp, EBODYPROL)); + + if (1 != pp && ! (MDOC_CALLABLE & mdoc_macros[tok].flags)) + return(perr(m, ln, pp, ENOCALL)); + + return((*mdoc_macros[tok].fp)(m, tok, ln, pp, pos, buf)); +} + + +static int +perr(struct mdoc *m, int line, int pos, enum merr type) +{ + char *p; + + p = NULL; + switch (type) { + case (ENOCALL): + p = "not callable"; + break; + case (EPROLBODY): + p = "macro disallowed in document body"; + break; + case (EBODYPROL): + p = "macro disallowed in document prologue"; + break; + case (EMALLOC): + p = "memory exhausted"; + break; + case (ETEXTPROL): + p = "text disallowed in document prologue"; + break; + case (ENOBLANK): + p = "blank lines disallowed in non-literal contexts"; + break; + case (ESPACE): + p = "whitespace disallowed after delimiter"; + break; + } + assert(p); + return(mdoc_perr(m, line, pos, p)); +} + + +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 */ + } + + 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 *mdoc, int line, + int pos, int tok, enum mdoc_type type) +{ + struct mdoc_node *p; + + if (NULL == (p = calloc(1, sizeof(struct mdoc_node)))) { + (void)verr(mdoc, EMALLOC); + return(NULL); + } + + p->sec = mdoc->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 *mdoc, int line, int pos, int tok) +{ + struct mdoc_node *p; + + p = node_alloc(mdoc, line, pos, tok, MDOC_TAIL); + if (NULL == p) + return(0); + return(node_append(mdoc, p)); +} + + +int +mdoc_head_alloc(struct mdoc *mdoc, int line, int pos, int tok) +{ + struct mdoc_node *p; + + assert(mdoc->first); + assert(mdoc->last); + + p = node_alloc(mdoc, line, pos, tok, MDOC_HEAD); + if (NULL == p) + return(0); + return(node_append(mdoc, p)); +} + + +int +mdoc_body_alloc(struct mdoc *mdoc, int line, int pos, int tok) +{ + struct mdoc_node *p; + + p = node_alloc(mdoc, line, pos, tok, MDOC_BODY); + if (NULL == p) + return(0); + return(node_append(mdoc, p)); +} + + +int +mdoc_block_alloc(struct mdoc *mdoc, int line, int pos, + int tok, struct mdoc_arg *args) +{ + struct mdoc_node *p; + + p = node_alloc(mdoc, line, pos, tok, MDOC_BLOCK); + if (NULL == p) + return(0); + if ((p->args = args)) + (args->refcnt)++; + return(node_append(mdoc, p)); +} + + +int +mdoc_elem_alloc(struct mdoc *mdoc, int line, int pos, + int tok, struct mdoc_arg *args) +{ + struct mdoc_node *p; + + p = node_alloc(mdoc, line, pos, tok, MDOC_ELEM); + if (NULL == p) + return(0); + if ((p->args = args)) + (args->refcnt)++; + return(node_append(mdoc, p)); +} + + +int +mdoc_word_alloc(struct mdoc *mdoc, + int line, int pos, const char *word) +{ + struct mdoc_node *p; + + p = node_alloc(mdoc, line, pos, -1, MDOC_TEXT); + if (NULL == p) + return(0); + if (NULL == (p->string = strdup(word))) { + (void)verr(mdoc, EMALLOC); + return(0); + } + return(node_append(mdoc, p)); +} + + +void +mdoc_node_free(struct mdoc_node *p) +{ + + 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); + + 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) +{ + + if (SEC_PROLOGUE == m->lastnamed) + return(perr(m, line, 0, ETEXTPROL)); + + if (0 == buf[0] && ! (MDOC_LITERAL & m->flags)) + return(perr(m, line, 0, ENOBLANK)); + + if ( ! mdoc_word_alloc(m, line, 0, buf)) + 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_perr(m, ln, 1, + "unknown macro: %s%s", + buf, strlen(buf) > 3 ? "..." : "")); + return(mdoc_pwarn(m, ln, 1, WARN_SYNTAX, + "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, c; + char mac[5]; + + /* Comments and empties are quickly ignored. */ + + if (0 == buf[1]) + return(1); + + if (' ' == buf[1]) { + i = 2; + while (buf[i] && ' ' == buf[i]) + i++; + if (0 == buf[i]) + return(1); + return(perr(m, ln, 1, ESPACE)); + } + + if (buf[1] && '\\' == buf[1]) + if (buf[2] && '\"' == buf[2]) + return(1); + + /* Copy the first word into a nil-terminated buffer. */ + + for (i = 1; i < 5; i++) { + if (0 == (mac[i - 1] = buf[i])) + break; + else if (' ' == buf[i]) + break; + } + + mac[i - 1] = 0; + + if (i == 5 || i <= 2) { + if ( ! macrowarn(m, ln, mac)) + goto err; + return(1); + } + + if (MDOC_MAX == (c = mdoc_hash_find(m->htab, 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. */ + + 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 00000000000..168e1fff6f2 --- /dev/null +++ b/usr.bin/mandoc/mdoc.h @@ -0,0 +1,328 @@ +/* $Id: mdoc.h,v 1.1 2009/04/06 20:30:40 kristaps Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@openbsd.org> + * + * 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 <time.h> + +/* + * 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___ 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_Ap 107 +#define MDOC_Lp 108 +#define MDOC_Lk 109 +#define MDOC_Mt 110 +#define MDOC_Brq 111 +#define MDOC_Bro 112 +#define MDOC_Brc 113 +#define MDOC__C 114 +#define MDOC_Es 115 +#define MDOC_En 116 +#define MDOC_Dx 117 +#define MDOC__Q 118 +#define MDOC_MAX 119 + +/* 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_ARG_MAX 26 + +/* Warnings are either syntax or groff-compatibility. */ +enum mdoc_warn { + WARN_SYNTAX, + WARN_COMPAT +}; + +/* 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_PROLOGUE = 0, + SEC_BODY = 1, + SEC_NAME = 2, + SEC_LIBRARY = 3, + SEC_SYNOPSIS = 4, + SEC_DESCRIPTION = 5, + SEC_IMPLEMENTATION = 6, + SEC_RETURN_VALUES = 7, + SEC_ENVIRONMENT = 8, + SEC_FILES = 9, + SEC_EXAMPLES = 10, + SEC_DIAGNOSTICS = 11, + SEC_COMPATIBILITY = 12, + SEC_ERRORS = 13, + SEC_SEE_ALSO = 14, + SEC_STANDARDS = 15, + SEC_HISTORY = 16, + SEC_AUTHORS = 17, + SEC_CAVEATS = 18, + SEC_BUGS = 19, + SEC_CUSTOM +}; + +/* 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 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; + + /* FIXME: union/struct this with #defines. */ + 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 { + void (*mdoc_msg)(void *, int, int, const char *); + int (*mdoc_err)(void *, int, int, const char *); + int (*mdoc_warn)(void *, int, int, + enum mdoc_warn, const char *); +}; + +/* Global table of macro names (`Bd', `Ed', etc.). */ +extern const char *const *mdoc_macronames; + +/* Global table of argument names (`column', `tag', etc.). */ +extern const char *const *mdoc_argnames; + +__BEGIN_DECLS + +struct mdoc; + +/* Free memory allocated with mdoc_alloc. */ +void mdoc_free(struct mdoc *); + +/* Allocate a new parser instance. */ +struct mdoc *mdoc_alloc(void *, int, const struct mdoc_cb *); + +/* Gets system ready for another parse. */ +int mdoc_reset(struct mdoc *); + +/* Parse a single line in a stream (boolean retval). */ +int mdoc_parseln(struct mdoc *, int, char *buf); + +/* Get result first node (after mdoc_endparse!). */ +const struct mdoc_node *mdoc_node(const struct mdoc *); + +/* Get result meta-information (after mdoc_endparse!). */ +const struct mdoc_meta *mdoc_meta(const struct mdoc *); + +/* Signal end of parse sequence (boolean retval). */ +int mdoc_endparse(struct mdoc *); + +/* The following are utility functions. */ + +const char *mdoc_a2att(const char *); +const char *mdoc_a2lib(const char *); +const char *mdoc_a2st(const char *); + +__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 00000000000..b45a57ecb47 --- /dev/null +++ b/usr.bin/mandoc/mdoc_action.c @@ -0,0 +1,783 @@ +/* $Id: mdoc_action.c,v 1.1 2009/04/06 20:30:40 kristaps Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@openbsd.org> + * + * 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 <sys/utsname.h> + +#include <assert.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "libmdoc.h" + +enum mwarn { + WBADSEC, + WNOWIDTH, + WBADDATE +}; + +enum merr { + ETOOLONG, + EMALLOC, + ENUMFMT +}; + +#define PRE_ARGS struct mdoc *m, const struct mdoc_node *n +#define POST_ARGS struct mdoc *m + +struct actions { + int (*pre)(PRE_ARGS); + int (*post)(POST_ARGS); +}; + +static int pwarn(struct mdoc *, int, int, enum mwarn); +static int perr(struct mdoc *, int, int, enum merr); +static int concat(struct mdoc *, const struct mdoc_node *, + char *, size_t); + +static int post_ar(POST_ARGS); +static int post_bl(POST_ARGS); +static int post_bl_width(POST_ARGS); +static int post_bl_tagwidth(POST_ARGS); +static int post_dd(POST_ARGS); +static int post_display(POST_ARGS); +static int post_dt(POST_ARGS); +static int post_nm(POST_ARGS); +static int post_os(POST_ARGS); +static int post_prol(POST_ARGS); +static int post_sh(POST_ARGS); +static int post_std(POST_ARGS); + +static int pre_bd(PRE_ARGS); +static int pre_dl(PRE_ARGS); + +#define vwarn(m, t) pwarn((m), (m)->last->line, (m)->last->pos, (t)) +#define verr(m, t) perr((m), (m)->last->line, (m)->last->pos, (t)) +#define nerr(m, n, t) perr((m), (n)->line, (n)->pos, (t)) + +const struct actions mdoc_actions[MDOC_MAX] = { + { NULL, NULL }, /* \" */ + { 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 */ + { NULL, 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, NULL }, /* Pa */ + { NULL, post_std }, /* Rv */ + { NULL, NULL }, /* 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, NULL }, /* 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, NULL }, /* 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, NULL }, /* Lb */ + { NULL, NULL }, /* Ap */ + { NULL, NULL }, /* Lp */ + { NULL, NULL }, /* 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 */ +}; + + +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)); +} + + +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(nerr(m, n, ETOOLONG)); + if (NULL == n->next) + continue; + if (strlcat(buf, " ", sz) >= sz) + return(nerr(m, n, ETOOLONG)); + } + + return(1); +} + + +static int +perr(struct mdoc *m, int line, int pos, enum merr type) +{ + char *p; + + p = NULL; + switch (type) { + case (ENUMFMT): + p = "bad number format"; + break; + case (ETOOLONG): + p = "argument text too long"; + break; + case (EMALLOC): + p = "memory exhausted"; + break; + } + assert(p); + return(mdoc_perr(m, line, pos, p)); +} + + +static int +pwarn(struct mdoc *m, int line, int pos, enum mwarn type) +{ + char *p; + int c; + + p = NULL; + c = WARN_SYNTAX; + switch (type) { + case (WBADSEC): + p = "inappropriate document section in manual section"; + c = WARN_COMPAT; + break; + case (WNOWIDTH): + p = "cannot determine default width"; + break; + case (WBADDATE): + p = "malformed date syntax"; + break; + } + assert(p); + return(mdoc_pwarn(m, line, pos, c, p)); +} + + +static int +post_std(POST_ARGS) +{ + + /* + * If '-std' is invoked without an argument, fill it in with our + * name (if it's been set). + */ + + if (NULL == m->last->args) + return(1); + if (m->last->args->argv[0].sz) + return(1); + + assert(m->meta.name); + + m->last->args->argv[0].value = calloc(1, sizeof(char *)); + if (NULL == m->last->args->argv[0].value) + return(verr(m, EMALLOC)); + + m->last->args->argv[0].sz = 1; + m->last->args->argv[0].value[0] = strdup(m->meta.name); + if (NULL == m->last->args->argv[0].value[0]) + return(verr(m, EMALLOC)); + + return(1); +} + + +static int +post_nm(POST_ARGS) +{ + char buf[64]; + + if (m->meta.name) + return(1); + + buf[0] = 0; + if ( ! concat(m, m->last->child, buf, sizeof(buf))) + return(0); + if (NULL == (m->meta.name = strdup(buf))) + return(verr(m, EMALLOC)); + 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 != m->last->type) + return(1); + + buf[0] = 0; + if ( ! concat(m, m->last->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(vwarn(m, WBADSEC)); + } + break; + default: + break; + } + return(1); +} + + +static int +post_dt(POST_ARGS) +{ + struct mdoc_node *n; + 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 == (n = m->last->child)) { + if (NULL == (m->meta.title = strdup("unknown"))) + return(verr(m, EMALLOC)); + if (NULL == (m->meta.vol = strdup("local"))) + return(verr(m, EMALLOC)); + return(post_prol(m)); + } + + /* Handles: `.Dt TITLE' + * --> title = TITLE, volume = local, msec = 0, arch = NULL + */ + + if (NULL == (m->meta.title = strdup(n->string))) + return(verr(m, EMALLOC)); + + if (NULL == (n = n->next)) { + if (NULL == (m->meta.vol = strdup("local"))) + return(verr(m, EMALLOC)); + return(post_prol(m)); + } + + /* 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(n->string); + if (cp) { + if (NULL == (m->meta.vol = strdup(cp))) + return(verr(m, EMALLOC)); + errno = 0; + lval = strtol(n->string, &ep, 10); + if (n->string[0] != '\0' && *ep == '\0') + m->meta.msec = (int)lval; + } else if (NULL == (m->meta.vol = strdup(n->string))) + return(verr(m, EMALLOC)); + + if (NULL == (n = n->next)) + return(post_prol(m)); + + /* Handles: `.Dt TITLE SEC VOL' + * --> title = TITLE, volume = VOL is vol ? + * format(VOL) : + * VOL is arch ? format(arch) : + * VOL + */ + + cp = mdoc_a2vol(n->string); + if (cp) { + free(m->meta.vol); + if (NULL == (m->meta.vol = strdup(cp))) + return(verr(m, EMALLOC)); + n = n->next; + } else { + cp = mdoc_a2arch(n->string); + if (NULL == cp) { + free(m->meta.vol); + if (NULL == (m->meta.vol = strdup(n->string))) + return(verr(m, EMALLOC)); + } else if (NULL == (m->meta.arch = strdup(cp))) + return(verr(m, EMALLOC)); + } + + /* Ignore any subsequent parameters... */ + + return(post_prol(m)); +} + + +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, m->last->child, buf, sizeof(buf))) + return(0); + + if (0 == buf[0]) { + if (-1 == uname(&utsname)) + return(mdoc_err(m, "utsname")); + if (strlcat(buf, utsname.sysname, 64) >= 64) + return(verr(m, ETOOLONG)); + if (strlcat(buf, " ", 64) >= 64) + return(verr(m, ETOOLONG)); + if (strlcat(buf, utsname.release, 64) >= 64) + return(verr(m, ETOOLONG)); + } + + if (NULL == (m->meta.os = strdup(buf))) + return(verr(m, EMALLOC)); + m->lastnamed = m->lastsec = SEC_BODY; + + return(post_prol(m)); +} + + +/* + * Calculate the -width for a `Bl -tag' list if it hasn't been provided. + * Uses the first head macro. + */ +static int +post_bl_tagwidth(struct mdoc *m) +{ + struct mdoc_node *n; + int sz; + char buf[32]; + + /* + * Use the text width, if a text node, or the default macro + * width if a macro. + */ + + if ((n = m->last->body->child)) { + assert(MDOC_BLOCK == n->type); + assert(MDOC_It == n->tok); + n = n->head->child; + } + + sz = 10; /* Default size. */ + + if (n) { + if (MDOC_TEXT != n->type) { + if (0 == (sz = (int)mdoc_macro2len(n->tok))) + if ( ! vwarn(m, WNOWIDTH)) + return(0); + } else + sz = (int)strlen(n->string) + 1; + } + + if (-1 == snprintf(buf, sizeof(buf), "%dn", sz)) + return(verr(m, ENUMFMT)); + + /* + * We have to dynamically add this to the macro's argument list. + * We're guaranteed that a MDOC_Width doesn't already exist. + */ + + n = m->last; + assert(n->args); + sz = (int)(n->args->argc)++; + + n->args->argv = realloc(n->args->argv, + n->args->argc * sizeof(struct mdoc_argv)); + + if (NULL == n->args->argv) + return(verr(m, EMALLOC)); + + n->args->argv[sz].arg = MDOC_Width; + n->args->argv[sz].line = m->last->line; + n->args->argv[sz].pos = m->last->pos; + n->args->argv[sz].sz = 1; + n->args->argv[sz].value = calloc(1, sizeof(char *)); + + if (NULL == n->args->argv[sz].value) + return(verr(m, EMALLOC)); + if (NULL == (n->args->argv[sz].value[0] = strdup(buf))) + return(verr(m, EMALLOC)); + + return(1); +} + + +static int +post_bl_width(struct mdoc *m) +{ + size_t width; + int i, tok; + char buf[32]; + char *p; + + if (NULL == m->last->args) + return(1); + + for (i = 0; i < (int)m->last->args->argc; i++) + if (MDOC_Width == m->last->args->argv[i].arg) + break; + + if (i == (int)m->last->args->argc) + return(1); + p = m->last->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 = 8; + else if (MDOC_MAX == (tok = mdoc_hash_find(m->htab, p))) + return(1); + else if (0 == (width = mdoc_macro2len(tok))) + return(vwarn(m, WNOWIDTH)); + + /* The value already exists: free and reallocate it. */ + + if (-1 == snprintf(buf, sizeof(buf), "%zun", width)) + return(verr(m, ENUMFMT)); + + free(m->last->args->argv[i].value[0]); + m->last->args->argv[i].value[0] = strdup(buf); + if (NULL == m->last->args->argv[i].value[0]) + return(verr(m, EMALLOC)); + + return(1); +} + + +static int +post_bl(POST_ARGS) +{ + int i, r, len; + + if (MDOC_BLOCK != m->last->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)(m->last->args ? m->last->args->argc : 0); + + for (r = i = 0; i < len; i++) { + if (MDOC_Tag == m->last->args->argv[i].arg) + r |= 1 << 0; + if (MDOC_Width == m->last->args->argv[i].arg) + r |= 1 << 1; + } + + if (r & (1 << 0) && ! (r & (1 << 1))) { + if ( ! post_bl_tagwidth(m)) + return(0); + } else if (r & (1 << 1)) + if ( ! post_bl_width(m)) + return(0); + + return(1); +} + + +static int +post_ar(POST_ARGS) +{ + struct mdoc_node *n; + + if (m->last->child) + return(1); + + n = m->last; + m->next = MDOC_NEXT_CHILD; + if ( ! mdoc_word_alloc(m, m->last->line, + m->last->pos, "file")) + return(0); + m->next = MDOC_NEXT_SIBLING; + if ( ! mdoc_word_alloc(m, m->last->line, + m->last->pos, "...")) + return(0); + + m->last = n; + m->next = MDOC_NEXT_SIBLING; + return(1); +} + + +static int +post_dd(POST_ARGS) +{ + char buf[64]; + + buf[0] = 0; + if ( ! concat(m, m->last->child, buf, sizeof(buf))) + return(0); + + if (0 == (m->meta.date = mdoc_atotime(buf))) { + if ( ! vwarn(m, WBADDATE)) + return(0); + m->meta.date = time(NULL); + } + + return(post_prol(m)); +} + + +static int +post_prol(POST_ARGS) +{ + struct mdoc_node *n; + + /* + * 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 = m->last->prev; + if (m->last->prev) + m->last->prev->next = NULL; + + n = m->last; + assert(NULL == m->last->next); + + if (m->last->prev) { + m->last = m->last->prev; + m->next = MDOC_NEXT_SIBLING; + } else { + m->last = m->last->parent; + m->next = MDOC_NEXT_CHILD; + } + + mdoc_node_freelist(n); + return(1); +} + + +static int +pre_dl(PRE_ARGS) +{ + + if (MDOC_BODY != n->type) + return(1); + m->flags |= MDOC_LITERAL; + return(1); +} + + +static int +pre_bd(PRE_ARGS) +{ + int i; + + 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) + break; + else if (MDOC_Unfilled == n->args->argv[i].arg) + break; + + if (i < (int)n->args->argc) + m->flags |= MDOC_LITERAL; + + return(1); +} + + +static int +post_display(POST_ARGS) +{ + + if (MDOC_BODY == m->last->type) + m->flags &= ~MDOC_LITERAL; + return(1); +} + + diff --git a/usr.bin/mandoc/mdoc_argv.c b/usr.bin/mandoc/mdoc_argv.c new file mode 100644 index 00000000000..0483481bc5f --- /dev/null +++ b/usr.bin/mandoc/mdoc_argv.c @@ -0,0 +1,872 @@ +/* $Id: mdoc_argv.c,v 1.1 2009/04/06 20:30:40 kristaps Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@openbsd.org> + * + * 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 <sys/types.h> + +#include <assert.h> +#include <ctype.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#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 ARGS_QUOTED (1 << 0) +#define ARGS_DELIM (1 << 1) +#define ARGS_TABSEP (1 << 2) +#define ARGS_ARGVLIKE (1 << 3) + +#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 + +enum mwarn { + WQUOTPARM, + WARGVPARM, + WCOLEMPTY, + WTAILWS +}; + +enum merr { + EQUOTTERM, + EMALLOC, + EARGVAL +}; + +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 *); +static int pwarn(struct mdoc *, int, int, enum mwarn); +static int perr(struct mdoc *, int, int, enum merr); + +#define verr(m, t) perr((m), (m)->last->line, (m)->last->pos, (t)) + +/* 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_NONE, /* MDOC_File */ + ARGV_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_OPT_SINGLE, /* 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, /* \" */ + 0, /* Dd */ + 0, /* Dt */ + 0, /* Os */ + ARGS_QUOTED, /* Sh */ + ARGS_QUOTED, /* Ss */ + ARGS_DELIM, /* Pp */ + ARGS_DELIM, /* D1 */ + ARGS_DELIM | ARGS_QUOTED, /* Dl */ + 0, /* Bd */ + 0, /* Ed */ + 0, /* Bl */ + 0, /* El */ + 0, /* It */ + ARGS_DELIM, /* Ad */ + ARGS_DELIM, /* An */ + ARGS_DELIM | ARGS_QUOTED, /* Ar */ + ARGS_QUOTED, /* Cd */ + ARGS_DELIM, /* Cm */ + ARGS_DELIM, /* Dv */ + ARGS_DELIM, /* Er */ + ARGS_DELIM, /* Ev */ + 0, /* Ex */ + ARGS_DELIM | ARGS_QUOTED, /* Fa */ + 0, /* Fd */ + ARGS_DELIM, /* Fl */ + ARGS_DELIM | ARGS_QUOTED, /* Fn */ + ARGS_DELIM | ARGS_QUOTED, /* Ft */ + ARGS_DELIM, /* Ic */ + 0, /* In */ + ARGS_DELIM | ARGS_QUOTED, /* Li */ + ARGS_QUOTED, /* Nd */ + ARGS_DELIM, /* Nm */ + ARGS_DELIM, /* Op */ + 0, /* Ot */ + ARGS_DELIM, /* Pa */ + 0, /* Rv */ + ARGS_DELIM | ARGS_ARGVLIKE, /* St */ + ARGS_DELIM, /* Va */ + ARGS_DELIM, /* Vt */ + ARGS_DELIM, /* Xr */ + ARGS_QUOTED, /* %A */ + ARGS_QUOTED, /* %B */ + ARGS_QUOTED, /* %D */ + ARGS_QUOTED, /* %I */ + ARGS_QUOTED, /* %J */ + ARGS_QUOTED, /* %N */ + ARGS_QUOTED, /* %O */ + ARGS_QUOTED, /* %P */ + ARGS_QUOTED, /* %R */ + ARGS_QUOTED, /* %T */ + ARGS_QUOTED, /* %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 | ARGS_QUOTED, /* Sy */ + ARGS_DELIM, /* Tn */ + ARGS_DELIM, /* Ux */ + ARGS_DELIM, /* Xc */ + 0, /* Xo */ + ARGS_QUOTED, /* Fo */ + 0, /* Fc */ + 0, /* Oo */ + ARGS_DELIM, /* Oc */ + 0, /* Bk */ + 0, /* Ek */ + 0, /* Bt */ + 0, /* Hf */ + 0, /* Fr */ + 0, /* Ud */ + 0, /* Lb */ + 0, /* Ap */ + ARGS_DELIM, /* Lp */ + ARGS_DELIM | ARGS_QUOTED, /* Lk */ + ARGS_DELIM | ARGS_QUOTED, /* Mt */ + ARGS_DELIM, /* Brq */ + 0, /* Bro */ + ARGS_DELIM, /* Brc */ + ARGS_QUOTED, /* %C */ + 0, /* Es */ + 0, /* En */ + 0, /* Dx */ + ARGS_QUOTED, /* %Q */ +}; + + +/* + * 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 *mdoc, int line, int tok, + struct mdoc_arg **v, int *pos, char *buf) +{ + int i; + char *p, sv; + struct mdoc_argv tmp; + struct mdoc_arg *arg; + + if (0 == buf[*pos]) + return(ARGV_EOLN); + + assert(' ' != buf[*pos]); + + if ('-' != buf[*pos] || ARGS_ARGVLIKE & mdoc_argflags[tok]) + return(ARGV_WORD); + + /* Parse through to the first unescaped space. */ + + i = *pos; + 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; + if ( ! pwarn(mdoc, line, i, WARGVPARM)) + return(ARGV_ERROR); + return(ARGV_WORD); + } + + while (buf[*pos] && ' ' == buf[*pos]) + (*pos)++; + + if ( ! argv(mdoc, line, &tmp, pos, buf)) + return(ARGV_ERROR); + + if (NULL == (arg = *v)) { + *v = calloc(1, sizeof(struct mdoc_arg)); + if (NULL == *v) { + (void)verr(mdoc, EMALLOC); + return(ARGV_ERROR); + } + arg = *v; + } + + arg->argc++; + arg->argv = realloc(arg->argv, arg->argc * + sizeof(struct mdoc_argv)); + + if (NULL == arg->argv) { + (void)verr(mdoc, 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; + /* LINTED */ + for (j = 0; j < (int)p->argv[i].sz; j++) + free(p->argv[i].value[j]); + + free(p->argv[i].value); + } + + free(p->argv); + free(p); +} + + + +static int +perr(struct mdoc *mdoc, int line, int pos, enum merr code) +{ + char *p; + + p = NULL; + switch (code) { + case (EMALLOC): + p = "memory exhausted"; + break; + case (EQUOTTERM): + p = "unterminated quoted parameter"; + break; + case (EARGVAL): + p = "argument requires a value"; + break; + } + assert(p); + return(mdoc_perr(mdoc, line, pos, p)); +} + + +static int +pwarn(struct mdoc *mdoc, int line, int pos, enum mwarn code) +{ + char *p; + int c; + + p = NULL; + c = WARN_SYNTAX; + switch (code) { + case (WQUOTPARM): + p = "unexpected quoted parameter"; + break; + case (WARGVPARM): + p = "argument-like parameter"; + break; + case (WCOLEMPTY): + p = "last list column is empty"; + c = WARN_COMPAT; + break; + case (WTAILWS): + p = "trailing whitespace"; + c = WARN_COMPAT; + break; + } + assert(p); + return(mdoc_pwarn(mdoc, line, pos, c, p)); +} + + +int +mdoc_args(struct mdoc *mdoc, 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]; + + /* + * Override per-macro argument flags with context-specific ones. + * As of now, this is only valid for `It' depending on its list + * context. + */ + + switch (tok) { + case (MDOC_It): + for (n = mdoc->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); + + /* + * Using `Bl -column' adds ARGS_TABSEP to the arguments + * and invalidates ARGS_DELIM. Using `Bl -diag' allows + * for quoted arguments. + */ + + /* LINTED */ + for (i = 0; i < c; i++) { + switch (n->args->argv[i].arg) { + case (MDOC_Column): + fl |= ARGS_TABSEP; + fl &= ~ARGS_DELIM; + i = c; + break; + case (MDOC_Diag): + fl |= ARGS_QUOTED; + i = c; + break; + default: + break; + } + } + break; + default: + break; + } + + return(args(mdoc, line, pos, buf, fl, v)); +} + + +static int +args(struct mdoc *mdoc, int line, + int *pos, char *buf, int fl, char **v) +{ + int i; + char *p, *pp; + + assert(*pos > 0); + + if (0 == buf[*pos]) + return(ARGS_EOLN); + + if ('\"' == buf[*pos] && ! (fl & ARGS_QUOTED)) + if ( ! pwarn(mdoc, line, *pos, WQUOTPARM)) + return(ARGS_ERROR); + + if ( ! (fl & ARGS_ARGVLIKE) && '-' == buf[*pos]) + if ( ! pwarn(mdoc, line, *pos, WARGVPARM)) + return(ARGS_ERROR); + + /* + * 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++; + /* There must be at least one space... */ + if (0 == buf[i] || ' ' != buf[i]) + break; + i++; + while (buf[i] && ' ' == buf[i]) + i++; + } + if (0 == buf[i]) { + *v = &buf[*pos]; + return(ARGS_PUNCT); + } + } + + /* First parse non-quoted strings. */ + + if ('\"' != buf[*pos] || ! (ARGS_QUOTED & fl)) { + *v = &buf[*pos]; + + /* + * Thar be dragons here! If we're tab-separated, search + * ahead for either a tab or the `Ta' macro. + * If a `Ta' is detected, it must be space-buffered before and + * after. If either of these hold true, then prune out the + * extra spaces and call it an argument. + */ + + if (ARGS_TABSEP & fl) { + /* Scan ahead to unescaped tab. */ + + 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; + } + + /* Choose delimiter tab/Ta. */ + + if (p && pp) + p = (p < pp ? p : pp); + else if ( ! p && pp) + p = pp; + + /* Strip delimiter's preceding whitespace. */ + + if (p && p > *v) { + pp = p - 1; + while (pp > *v && ' ' == *pp) + pp--; + if (pp == *v && ' ' == *pp) + *pp = 0; + else if (' ' == *pp) + *(pp + 1) = 0; + } + + /* ...in- and proceding whitespace. */ + + if (p && ('\t' != *p)) { + *p++ = 0; + *p++ = 0; + } else if (p) + *p++ = 0; + + if (p) { + while (' ' == *p) + p++; + if (0 != *p) + *(p - 1) = 0; + *pos += (int)(p - *v); + } + + if (p && 0 == *p) + if ( ! pwarn(mdoc, line, *pos, WCOLEMPTY)) + return(0); + if (p && 0 == *p && p > *v && ' ' == *(p - 1)) + if ( ! pwarn(mdoc, line, *pos, WTAILWS)) + return(0); + + if (p) + return(ARGS_PHRASE); + + /* Configure the eoln case, too. */ + + p = strchr(*v, 0); + assert(p); + + if (p > *v && ' ' == *(p - 1)) + if ( ! pwarn(mdoc, line, *pos, WTAILWS)) + return(0); + *pos += (int)(p - *v); + + return(ARGS_PHRASE); + } + + /* Do non-tabsep look-ahead here. */ + + if ( ! (ARGS_TABSEP & fl)) + while (buf[*pos]) { + if (' ' == buf[*pos]) + if ('\\' != buf[*pos - 1]) + break; + (*pos)++; + } + + if (0 == buf[*pos]) + return(ARGS_WORD); + + buf[(*pos)++] = 0; + + if (0 == buf[*pos]) + return(ARGS_WORD); + + if ( ! (ARGS_TABSEP & fl)) + while (buf[*pos] && ' ' == buf[*pos]) + (*pos)++; + + if (buf[*pos]) + return(ARGS_WORD); + + if ( ! pwarn(mdoc, line, *pos, WTAILWS)) + return(ARGS_ERROR); + + return(ARGS_WORD); + } + + /* + * If we're a quoted string (and quoted strings are allowed), + * then parse ahead to the next quote. If none's found, it's an + * error. After, parse to the next word. + */ + + *v = &buf[++(*pos)]; + + while (buf[*pos] && '\"' != buf[*pos]) + (*pos)++; + + if (0 == buf[*pos]) { + (void)perr(mdoc, line, *pos, EQUOTTERM); + return(ARGS_ERROR); + } + + buf[(*pos)++] = 0; + if (0 == buf[*pos]) + return(ARGS_QWORD); + + while (buf[*pos] && ' ' == buf[*pos]) + (*pos)++; + + if (buf[*pos]) + return(ARGS_QWORD); + + if ( ! pwarn(mdoc, line, *pos, WTAILWS)) + return(ARGS_ERROR); + + return(ARGS_QWORD); +} + + +static int +argv_a2arg(int tok, const char *argv) +{ + + /* + * 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(argv, "split")) + return(MDOC_Split); + else if (0 == strcmp(argv, "nosplit")) + return(MDOC_Nosplit); + break; + + case (MDOC_Bd): + if (0 == strcmp(argv, "ragged")) + return(MDOC_Ragged); + else if (0 == strcmp(argv, "unfilled")) + return(MDOC_Unfilled); + else if (0 == strcmp(argv, "filled")) + return(MDOC_Filled); + else if (0 == strcmp(argv, "literal")) + return(MDOC_Literal); + else if (0 == strcmp(argv, "file")) + return(MDOC_File); + else if (0 == strcmp(argv, "offset")) + return(MDOC_Offset); + else if (0 == strcmp(argv, "compact")) + return(MDOC_Compact); + break; + + case (MDOC_Bf): + if (0 == strcmp(argv, "emphasis")) + return(MDOC_Emphasis); + else if (0 == strcmp(argv, "literal")) + return(MDOC_Literal); + else if (0 == strcmp(argv, "symbolic")) + return(MDOC_Symbolic); + break; + + case (MDOC_Bk): + if (0 == strcmp(argv, "words")) + return(MDOC_Words); + break; + + case (MDOC_Bl): + if (0 == strcmp(argv, "bullet")) + return(MDOC_Bullet); + else if (0 == strcmp(argv, "dash")) + return(MDOC_Dash); + else if (0 == strcmp(argv, "hyphen")) + return(MDOC_Hyphen); + else if (0 == strcmp(argv, "item")) + return(MDOC_Item); + else if (0 == strcmp(argv, "enum")) + return(MDOC_Enum); + else if (0 == strcmp(argv, "tag")) + return(MDOC_Tag); + else if (0 == strcmp(argv, "diag")) + return(MDOC_Diag); + else if (0 == strcmp(argv, "hang")) + return(MDOC_Hang); + else if (0 == strcmp(argv, "ohang")) + return(MDOC_Ohang); + else if (0 == strcmp(argv, "inset")) + return(MDOC_Inset); + else if (0 == strcmp(argv, "column")) + return(MDOC_Column); + else if (0 == strcmp(argv, "width")) + return(MDOC_Width); + else if (0 == strcmp(argv, "offset")) + return(MDOC_Offset); + else if (0 == strcmp(argv, "compact")) + return(MDOC_Compact); + else if (0 == strcmp(argv, "nested")) + return(MDOC_Nested); + break; + + case (MDOC_Rv): + /* FALLTHROUGH */ + case (MDOC_Ex): + if (0 == strcmp(argv, "std")) + return(MDOC_Std); + break; + default: + break; + } + + return(MDOC_ARG_MAX); +} + + +static int +argv_multi(struct mdoc *mdoc, int line, + struct mdoc_argv *v, int *pos, char *buf) +{ + int c, ppos; + char *p; + + ppos = *pos; + + for (v->sz = 0; ; v->sz++) { + if ('-' == buf[*pos]) + break; + c = args(mdoc, line, pos, buf, ARGS_QUOTED, &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)verr(mdoc, EMALLOC); + return(ARGV_ERROR); + } + } + if (NULL == (v->value[(int)v->sz] = strdup(p))) + return(verr(mdoc, EMALLOC)); + } + + if (v->sz) + return(1); + + return(perr(mdoc, line, ppos, EARGVAL)); +} + + +static int +argv_opt_single(struct mdoc *mdoc, int line, + struct mdoc_argv *v, int *pos, char *buf) +{ + int c; + char *p; + + if ('-' == buf[*pos]) + return(1); + + c = args(mdoc, line, pos, buf, ARGS_QUOTED, &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(verr(mdoc, EMALLOC)); + if (NULL == (v->value[0] = strdup(p))) + return(verr(mdoc, EMALLOC)); + + return(1); +} + + +/* + * Parse a single, mandatory value from the stream. + */ +static int +argv_single(struct mdoc *mdoc, int line, + struct mdoc_argv *v, int *pos, char *buf) +{ + int c, ppos; + char *p; + + ppos = *pos; + + c = args(mdoc, line, pos, buf, ARGS_QUOTED, &p); + if (ARGS_ERROR == c) + return(0); + if (ARGS_EOLN == c) + return(perr(mdoc, line, ppos, EARGVAL)); + + v->sz = 1; + if (NULL == (v->value = calloc(1, sizeof(char *)))) + return(verr(mdoc, EMALLOC)); + if (NULL == (v->value[0] = strdup(p))) + return(verr(mdoc, 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 00000000000..73602bf2e0a --- /dev/null +++ b/usr.bin/mandoc/mdoc_hash.c @@ -0,0 +1,174 @@ +/* $Id: mdoc_hash.c,v 1.1 2009/04/06 20:30:40 kristaps Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@openbsd.org> + * + * 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 <assert.h> +#include <ctype.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include "libmdoc.h" + +/* + * Routines for the perfect-hash hashtable used by the parser to look up + * tokens by their string-ified names (`.Fl' -> MDOC_Fl). The + * allocation penalty for this is 27 * 26 * sizeof(ptr). + */ + +void +mdoc_hash_free(void *htab) +{ + + free(htab); +} + + +void * +mdoc_hash_alloc(void) +{ + int i, major, minor, ind; + const void **htab; + + htab = calloc(27 * 26 * 3, sizeof(struct mdoc_macro *)); + if (NULL == htab) + return(NULL); + + for (i = 1; i < MDOC_MAX; i++) { + major = mdoc_macronames[i][0]; + assert((major >= 65 && major <= 90) || + major == 37); + + if (major == 37) + major = 0; + else + major -= 64; + + minor = mdoc_macronames[i][1]; + assert((minor >= 65 && minor <= 90) || + (minor == 49) || + (minor >= 97 && minor <= 122)); + + if (minor == 49) + minor = 0; + else if (minor <= 90) + minor -= 65; + else + minor -= 97; + + assert(major >= 0 && major < 27); + assert(minor >= 0 && minor < 26); + + ind = (major * 27 * 3) + (minor * 3); + + if (NULL == htab[ind]) { + htab[ind] = &mdoc_macros[i]; + continue; + } + + if (NULL == htab[++ind]) { + htab[ind] = &mdoc_macros[i]; + continue; + } + + assert(NULL == htab[++ind]); + htab[ind] = &mdoc_macros[i]; + } + + return((void *)htab); +} + + +int +mdoc_hash_find(const void *arg, const char *tmp) +{ + int major, minor, ind, slot; + const void **htab; + + htab = /* LINTED */ + (const void **)arg; + + if (0 == tmp[0] || 0 == tmp[1]) + return(MDOC_MAX); + if (tmp[2] && tmp[3]) + return(MDOC_MAX); + + if ( ! (tmp[0] == 37 || (tmp[0] >= 65 && tmp[0] <= 90))) + return(MDOC_MAX); + + if ( ! ((tmp[1] >= 65 && tmp[1] <= 90) || + (tmp[1] == 49) || + (tmp[1] >= 97 && tmp[1] <= 122))) + return(MDOC_MAX); + + if (tmp[0] == 37) + major = 0; + else + major = tmp[0] - 64; + + if (tmp[1] == 49) + minor = 0; + else if (tmp[1] <= 90) + minor = tmp[1] - 65; + else + minor = tmp[1] - 97; + + ind = (major * 27 * 3) + (minor * 3); + if (ind < 0 || ind >= (27 * 26 * 3)) + return(MDOC_MAX); + + if (htab[ind]) { + slot = htab[ind] - /* LINTED */ + (void *)mdoc_macros; + assert(0 == (size_t)slot % sizeof(struct mdoc_macro)); + slot /= sizeof(struct mdoc_macro); + if (mdoc_macronames[slot][0] == tmp[0] && + mdoc_macronames[slot][1] == tmp[1] && + (0 == tmp[2] || + mdoc_macronames[slot][2] == tmp[2])) + return(slot); + ind++; + } + + if (htab[ind]) { + slot = htab[ind] - /* LINTED */ + (void *)mdoc_macros; + assert(0 == (size_t)slot % sizeof(struct mdoc_macro)); + slot /= sizeof(struct mdoc_macro); + if (mdoc_macronames[slot][0] == tmp[0] && + mdoc_macronames[slot][1] == tmp[1] && + (0 == tmp[2] || + mdoc_macronames[slot][2] == tmp[2])) + return(slot); + ind++; + } + + if (NULL == htab[ind]) + return(MDOC_MAX); + slot = htab[ind] - /* LINTED */ + (void *)mdoc_macros; + assert(0 == (size_t)slot % sizeof(struct mdoc_macro)); + slot /= sizeof(struct mdoc_macro); + if (mdoc_macronames[slot][0] == tmp[0] && + mdoc_macronames[slot][1] == tmp[1] && + (0 == tmp[2] || + mdoc_macronames[slot][2] == tmp[2])) + return(slot); + + return(MDOC_MAX); +} + diff --git a/usr.bin/mandoc/mdoc_macro.c b/usr.bin/mandoc/mdoc_macro.c new file mode 100644 index 00000000000..769e3bace69 --- /dev/null +++ b/usr.bin/mandoc/mdoc_macro.c @@ -0,0 +1,1496 @@ +/* $Id: mdoc_macro.c,v 1.1 2009/04/06 20:30:40 kristaps Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@openbsd.org> + * + * 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 <assert.h> +#include <ctype.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include "libmdoc.h" + +/* FIXME: .Fl, .Ar, .Cd handling of `|'. */ + +enum mwarn { + WIGNE, + WIMPBRK, + WMACPARM, + WOBS +}; + +enum merr { + EOPEN, + EQUOT, + ENOCTX, + ENOPARMS +}; + +#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_impblock(struct mdoc *, int, int, int); +static int rew_expblock(struct mdoc *, int, int, int); +static int rew_subblock(enum mdoc_type, + struct mdoc *, int, int, int); +static int rew_last(struct mdoc *, struct mdoc_node *); +static int append_delims(struct mdoc *, int, int *, char *); +static int lookup(struct mdoc *, int, int, int, const char *); +static int pwarn(struct mdoc *, int, int, enum mwarn); +static int perr(struct mdoc *, int, int, enum merr); +static int swarn(struct mdoc *, enum mdoc_type, int, int, + const struct mdoc_node *); + +#define nerr(m, n, t) perr((m), (n)->line, (n)->pos, (t)) + +/* Central table of library: who gets parsed how. */ + +const struct mdoc_macro __mdoc_macros[MDOC_MAX] = { + { NULL, 0 }, /* \" */ + { 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, 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_PARSED }, /* An */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ar */ + { in_line_eoln, MDOC_CALLABLE }, /* 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_PARSED }, /* Ft */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ic */ + { in_line_eoln, 0 }, /* In */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Li */ + { in_line_eoln, 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_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_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Ap */ + { in_line, 0 }, /* Lp */ + { in_line, MDOC_PARSED }, /* Lk */ + { in_line, 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 */ +}; + +const struct mdoc_macro * const mdoc_macros = __mdoc_macros; + + +static int +perr(struct mdoc *mdoc, int line, int pos, enum merr type) +{ + char *p; + + p = NULL; + switch (type) { + case (EOPEN): + p = "explicit scope still open on exit"; + break; + case (EQUOT): + p = "unterminated quotation"; + break; + case (ENOCTX): + p = "closure has no prior context"; + break; + case (ENOPARMS): + p = "unexpect line arguments"; + break; + } + assert(p); + return(mdoc_perr(mdoc, line, pos, p)); +} + + +static int +pwarn(struct mdoc *mdoc, int line, int pos, enum mwarn type) +{ + char *p; + + p = NULL; + switch (type) { + case (WIGNE): + p = "ignoring empty element"; + break; + case (WIMPBRK): + p = "crufty end-of-line scope violation"; + break; + case (WMACPARM): + p = "macro-like parameter"; + break; + case (WOBS): + p = "macro marked obsolete"; + break; + } + assert(p); + return(mdoc_pwarn(mdoc, line, pos, WARN_SYNTAX, p)); +} + + +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 = "<root>"; + 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_perr(mdoc, line, pos, + "%s scope breaks %s scope of %s", + tt, t, n)); + return(mdoc_pwarn(mdoc, line, pos, WARN_SYNTAX, + "%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 *mdoc) +{ + struct mdoc_node *n; + + /* Scan for open explicit scopes. */ + + n = MDOC_VALID & mdoc->last->flags ? + mdoc->last->parent : mdoc->last; + + for ( ; n; n = n->parent) { + if (MDOC_BLOCK != n->type) + continue; + if ( ! (MDOC_EXPLICIT & mdoc_macros[n->tok].flags)) + continue; + return(nerr(mdoc, n, EOPEN)); + } + + return(rew_last(mdoc, mdoc->first)); +} + +static int +lookup(struct mdoc *mdoc, int line, int pos, int from, const char *p) +{ + int res; + + res = mdoc_hash_find(mdoc->htab, p); + if (MDOC_PARSED & mdoc_macros[from].flags) + return(res); + if (MDOC_MAX == res) + return(res); + if ( ! pwarn(mdoc, line, pos, WMACPARM)) + return(-1); + return(MDOC_MAX); +} + + +static int +rew_last(struct mdoc *mdoc, 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)); +} + + +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_HEAD != type); + 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_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_Ss): + return(MDOC_Ss == p->tok); + case (MDOC_Sh): + 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_subblock(enum mdoc_type type, struct mdoc *mdoc, + int tok, int line, int ppos) +{ + struct mdoc_node *n; + int c; + + /* LINTED */ + for (n = mdoc->last; n; n = n->parent) { + c = rew_dohalt(tok, type, n); + if (REWIND_HALT == c) + return(1); + if (REWIND_REWIND == c) + break; + else if (rew_dobreak(tok, n)) + continue; + if ( ! swarn(mdoc, type, line, ppos, n)) + return(0); + } + + assert(n); + return(rew_last(mdoc, n)); +} + + +static int +rew_expblock(struct mdoc *mdoc, int tok, int line, int ppos) +{ + struct mdoc_node *n; + int c; + + /* LINTED */ + for (n = mdoc->last; n; n = n->parent) { + c = rew_dohalt(tok, MDOC_BLOCK, n); + if (REWIND_HALT == c) + return(perr(mdoc, line, ppos, ENOCTX)); + if (REWIND_REWIND == c) + break; + else if (rew_dobreak(tok, n)) + continue; + if ( ! swarn(mdoc, MDOC_BLOCK, line, ppos, n)) + return(0); + } + + assert(n); + return(rew_last(mdoc, n)); +} + + +static int +rew_impblock(struct mdoc *mdoc, int tok, int line, int ppos) +{ + struct mdoc_node *n; + int c; + + /* LINTED */ + for (n = mdoc->last; n; n = n->parent) { + c = rew_dohalt(tok, MDOC_BLOCK, n); + if (REWIND_HALT == c) + return(1); + else if (REWIND_REWIND == c) + break; + else if (rew_dobreak(tok, n)) + continue; + if ( ! swarn(mdoc, MDOC_BLOCK, line, ppos, n)) + return(0); + } + + assert(n); + return(rew_last(mdoc, 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_args(mdoc, line, pos, buf, 0, &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); + mdoc->next = MDOC_NEXT_SIBLING; + } + + 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 (0 == buf[*pos]) { + if ( ! rew_subblock(MDOC_BODY, mdoc, + tok, line, ppos)) + return(0); + return(rew_expblock(mdoc, tok, line, ppos)); + } + return(perr(mdoc, line, ppos, ENOPARMS)); + } + + if ( ! rew_subblock(MDOC_BODY, mdoc, tok, line, ppos)) + return(0); + + if (maxargs > 0) { + if ( ! mdoc_tail_alloc(mdoc, line, + ppos, rew_alt(tok))) + return(0); + mdoc->next = MDOC_NEXT_CHILD; + } + + for (lastarg = ppos, flushed = j = 0; ; j++) { + lastarg = *pos; + + if (j == maxargs && ! flushed) { + if ( ! rew_expblock(mdoc, tok, line, ppos)) + return(0); + flushed = 1; + } + + c = mdoc_args(mdoc, line, pos, buf, tok, &p); + + if (ARGS_ERROR == c) + return(0); + if (ARGS_PUNCT == c) + break; + if (ARGS_EOLN == c) + break; + + if (-1 == (c = lookup(mdoc, line, lastarg, tok, p))) + return(0); + else if (MDOC_MAX != c) { + if ( ! flushed) { + if ( ! rew_expblock(mdoc, tok, + line, ppos)) + return(0); + flushed = 1; + } + if ( ! mdoc_macro(mdoc, c, line, lastarg, pos, buf)) + return(0); + break; + } + + if ( ! mdoc_word_alloc(mdoc, line, lastarg, p)) + return(0); + mdoc->next = MDOC_NEXT_SIBLING; + } + + if ( ! flushed && ! rew_expblock(mdoc, tok, line, ppos)) + return(0); + + if (ppos > 1) + return(1); + return(append_delims(mdoc, line, pos, buf)); +} + + +/* + * In-line macros where reserved words cause scope close-reopen. + */ +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_Lp): + /* FALLTHROUGH */ + case (MDOC_Pp): + /* FALLTHROUGH */ + case (MDOC_Nm): + /* FALLTHROUGH */ + case (MDOC_Fl): + /* FALLTHROUGH */ + case (MDOC_Ar): + nc = 1; + break; + default: + nc = 0; + break; + } + + for (la = ppos, arg = NULL;; ) { + la = *pos; + c = mdoc_argv(mdoc, 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(mdoc, 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(mdoc, line, la, 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 && -1 != c) { + if (0 == lastpunct && ! rew_elem(mdoc, tok)) + return(0); + if (nc && 0 == cnt) { + if ( ! mdoc_elem_alloc(mdoc, line, ppos, + tok, arg)) + return(0); + mdoc->next = MDOC_NEXT_SIBLING; + } else if ( ! nc && 0 == cnt) { + mdoc_argv_free(arg); + if ( ! pwarn(mdoc, line, ppos, WIGNE)) + return(0); + } + c = mdoc_macro(mdoc, c, line, la, pos, buf); + if (0 == c) + return(0); + if (ppos > 1) + return(1); + return(append_delims(mdoc, line, pos, buf)); + } else if (-1 == c) + return(0); + + /* + * 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(mdoc, tok)) + return(0); + lastpunct = 1; + } else if (lastpunct) { + c = mdoc_elem_alloc(mdoc, line, ppos, tok, arg); + if (0 == c) + return(0); + mdoc->next = MDOC_NEXT_CHILD; + lastpunct = 0; + } + + if ( ! d) + cnt++; + if ( ! mdoc_word_alloc(mdoc, line, la, p)) + return(0); + mdoc->next = MDOC_NEXT_SIBLING; + } + + if (0 == lastpunct && ! rew_elem(mdoc, 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) { + c = mdoc_elem_alloc(mdoc, line, ppos, tok, arg); + if (0 == c) + return(0); + mdoc->next = MDOC_NEXT_SIBLING; + } else if ( ! nc && 0 == cnt) { + mdoc_argv_free(arg); + if ( ! pwarn(mdoc, line, ppos, WIGNE)) + return(0); + } + + if (ppos > 1) + return(1); + return(append_delims(mdoc, line, pos, buf)); +} + + +/* + * Block full-explicit and full-implicit. + */ +static int +blk_full(MACRO_PROT_ARGS) +{ + int c, lastarg, reopen; + struct mdoc_arg *arg; + char *p; + + if ( ! (MDOC_EXPLICIT & mdoc_macros[tok].flags)) { + if ( ! rew_subblock(MDOC_BODY, mdoc, + tok, line, ppos)) + return(0); + if ( ! rew_impblock(mdoc, tok, line, ppos)) + return(0); + } + + for (arg = NULL;; ) { + lastarg = *pos; + c = mdoc_argv(mdoc, 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(mdoc, line, ppos, tok, arg)) + return(0); + mdoc->next = MDOC_NEXT_CHILD; + + if (0 == buf[*pos]) { + if ( ! mdoc_head_alloc(mdoc, line, ppos, tok)) + return(0); + if ( ! rew_subblock(MDOC_HEAD, mdoc, + tok, line, ppos)) + return(0); + if ( ! mdoc_body_alloc(mdoc, line, ppos, tok)) + return(0); + mdoc->next = MDOC_NEXT_CHILD; + return(1); + } + + if ( ! mdoc_head_alloc(mdoc, line, ppos, tok)) + return(0); + mdoc->next = MDOC_NEXT_CHILD; + + for (reopen = 0;; ) { + lastarg = *pos; + c = mdoc_args(mdoc, line, pos, buf, tok, &p); + + if (ARGS_ERROR == c) + return(0); + if (ARGS_EOLN == c) + break; + if (ARGS_PHRASE == c) { + if (reopen && ! mdoc_head_alloc + (mdoc, line, ppos, tok)) + return(0); + mdoc->next = MDOC_NEXT_CHILD; + /* + * Phrases are self-contained macro phrases used + * in the columnar output of a macro. They need + * special handling. + */ + if ( ! phrase(mdoc, line, lastarg, buf)) + return(0); + if ( ! rew_subblock(MDOC_HEAD, mdoc, + tok, line, ppos)) + return(0); + + reopen = 1; + continue; + } + + if (-1 == (c = lookup(mdoc, line, lastarg, tok, p))) + return(0); + + if (MDOC_MAX == c) { + if ( ! mdoc_word_alloc(mdoc, line, lastarg, p)) + return(0); + mdoc->next = MDOC_NEXT_SIBLING; + continue; + } + + if ( ! mdoc_macro(mdoc, c, line, lastarg, pos, buf)) + return(0); + break; + } + + if (1 == ppos && ! append_delims(mdoc, line, pos, buf)) + return(0); + if ( ! rew_subblock(MDOC_HEAD, mdoc, tok, line, ppos)) + return(0); + + if ( ! mdoc_body_alloc(mdoc, line, ppos, tok)) + return(0); + mdoc->next = MDOC_NEXT_CHILD; + + return(1); +} + + +/* + * Block partial-imnplicit scope. + */ +static int +blk_part_imp(MACRO_PROT_ARGS) +{ + int lastarg, c; + char *p; + struct mdoc_node *blk, *body, *n; + + if ( ! mdoc_block_alloc(mdoc, line, ppos, tok, NULL)) + return(0); + mdoc->next = MDOC_NEXT_CHILD; + blk = mdoc->last; + + if ( ! mdoc_head_alloc(mdoc, line, ppos, tok)) + return(0); + mdoc->next = MDOC_NEXT_SIBLING; + + if ( ! mdoc_body_alloc(mdoc, line, ppos, tok)) + return(0); + mdoc->next = MDOC_NEXT_CHILD; + body = mdoc->last; + + /* XXX - no known argument macros. */ + + for (lastarg = ppos;; ) { + lastarg = *pos; + c = mdoc_args(mdoc, 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 (-1 == (c = lookup(mdoc, line, lastarg, tok, p))) + return(0); + else if (MDOC_MAX == c) { + if ( ! mdoc_word_alloc(mdoc, line, lastarg, p)) + return(0); + mdoc->next = MDOC_NEXT_SIBLING; + continue; + } + + if ( ! mdoc_macro(mdoc, c, line, lastarg, pos, buf)) + return(0); + break; + } + + /* + * Since we know what our context is, we can rewind directly to + * it. This allows us to accomodate for our scope being + * violated by another token. + */ + + for (n = mdoc->last; n; n = n->parent) + if (body == n) + break; + + if (NULL == n && ! pwarn(mdoc, body->line, body->pos, WIMPBRK)) + return(0); + + if (n && ! rew_last(mdoc, body)) + return(0); + + if (1 == ppos && ! append_delims(mdoc, line, pos, buf)) + return(0); + + if (n && ! rew_last(mdoc, blk)) + return(0); + + return(1); +} + + +/* + * Block partial-explicit macros. + */ +static int +blk_part_exp(MACRO_PROT_ARGS) +{ + int lastarg, flushed, j, c, maxargs; + char *p; + + lastarg = ppos; + flushed = 0; + + /* + * Number of arguments (head arguments). Only `Eo' has these, + */ + + switch (tok) { + case (MDOC_Eo): + maxargs = 1; + break; + default: + maxargs = 0; + break; + } + + if ( ! mdoc_block_alloc(mdoc, line, ppos, tok, NULL)) + return(0); + mdoc->next = MDOC_NEXT_CHILD; + + if (0 == maxargs) { + if ( ! mdoc_head_alloc(mdoc, line, ppos, tok)) + return(0); + if ( ! rew_subblock(MDOC_HEAD, mdoc, + tok, line, ppos)) + return(0); + if ( ! mdoc_body_alloc(mdoc, line, ppos, tok)) + return(0); + flushed = 1; + } else if ( ! mdoc_head_alloc(mdoc, line, ppos, tok)) + return(0); + + mdoc->next = MDOC_NEXT_CHILD; + + for (j = 0; ; j++) { + lastarg = *pos; + if (j == maxargs && ! flushed) { + if ( ! rew_subblock(MDOC_HEAD, mdoc, + tok, line, ppos)) + return(0); + flushed = 1; + if ( ! mdoc_body_alloc(mdoc, line, ppos, tok)) + return(0); + mdoc->next = MDOC_NEXT_CHILD; + } + + c = mdoc_args(mdoc, 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 (-1 == (c = lookup(mdoc, line, lastarg, tok, p))) + return(0); + else if (MDOC_MAX != c) { + if ( ! flushed) { + if ( ! rew_subblock(MDOC_HEAD, mdoc, + tok, line, ppos)) + return(0); + flushed = 1; + if ( ! mdoc_body_alloc(mdoc, line, + ppos, tok)) + return(0); + mdoc->next = MDOC_NEXT_CHILD; + } + if ( ! mdoc_macro(mdoc, c, line, lastarg, + pos, buf)) + return(0); + break; + } + + if ( ! flushed && mdoc_isdelim(p)) { + if ( ! rew_subblock(MDOC_HEAD, mdoc, + tok, line, ppos)) + return(0); + flushed = 1; + if ( ! mdoc_body_alloc(mdoc, line, ppos, tok)) + return(0); + mdoc->next = MDOC_NEXT_CHILD; + } + + if ( ! mdoc_word_alloc(mdoc, line, lastarg, p)) + return(0); + mdoc->next = MDOC_NEXT_SIBLING; + } + + if ( ! flushed) { + if ( ! rew_subblock(MDOC_HEAD, mdoc, tok, line, ppos)) + return(0); + if ( ! mdoc_body_alloc(mdoc, line, ppos, tok)) + return(0); + mdoc->next = MDOC_NEXT_CHILD; + } + + if (ppos > 1) + return(1); + return(append_delims(mdoc, line, pos, buf)); +} + + +/* + * In-line macros where reserved words signal closure of the macro. + * Macros also have a fixed number of arguments. + */ +static int +in_line_argn(MACRO_PROT_ARGS) +{ + int lastarg, flushed, j, c, maxargs; + struct mdoc_arg *arg; + char *p; + + + /* + * Fixed maximum arguments per macro. Some of these have none + * and close as soon as the invocation is parsed. + */ + + switch (tok) { + case (MDOC_Ap): + /* FALLTHROUGH */ + case (MDOC_No): + /* FALLTHROUGH */ + case (MDOC_Ns): + /* FALLTHROUGH */ + case (MDOC_Ux): + maxargs = 0; + break; + default: + maxargs = 1; + break; + } + + for (lastarg = ppos, arg = NULL;; ) { + lastarg = *pos; + c = mdoc_argv(mdoc, 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_elem_alloc(mdoc, line, ppos, tok, arg)) + return(0); + mdoc->next = MDOC_NEXT_CHILD; + + for (flushed = j = 0; ; j++) { + lastarg = *pos; + + if (j == maxargs && ! flushed) { + if ( ! rew_elem(mdoc, tok)) + return(0); + flushed = 1; + } + + c = mdoc_args(mdoc, line, pos, buf, tok, &p); + + if (ARGS_ERROR == c) + return(0); + if (ARGS_PUNCT == c) + break; + if (ARGS_EOLN == c) + break; + + if (-1 == (c = lookup(mdoc, line, lastarg, tok, p))) + return(0); + else if (MDOC_MAX != c) { + if ( ! flushed && ! rew_elem(mdoc, tok)) + return(0); + flushed = 1; + if ( ! mdoc_macro(mdoc, c, line, lastarg, pos, buf)) + return(0); + break; + } + + if ( ! (MDOC_IGNDELIM & mdoc_macros[tok].flags) && + ! flushed && mdoc_isdelim(p)) { + if ( ! rew_elem(mdoc, tok)) + return(0); + flushed = 1; + } + + if ( ! mdoc_word_alloc(mdoc, line, lastarg, p)) + return(0); + mdoc->next = MDOC_NEXT_SIBLING; + } + + if ( ! flushed && ! rew_elem(mdoc, tok)) + return(0); + + if (ppos > 1) + return(1); + return(append_delims(mdoc, line, pos, buf)); +} + + +/* + * In-line macro that spans an entire line. May be callable, but has no + * subsequent parsed arguments. + */ +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)); + + arg = NULL; + + for (;;) { + la = *pos; + c = mdoc_argv(mdoc, 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); + } + + if ( ! mdoc_elem_alloc(mdoc, line, ppos, tok, arg)) + return(0); + + mdoc->next = MDOC_NEXT_CHILD; + + for (;;) { + la = *pos; + w = mdoc_args(mdoc, line, pos, buf, tok, &p); + + if (ARGS_ERROR == w) + return(0); + if (ARGS_EOLN == w) + break; + + c = ARGS_QWORD == w ? MDOC_MAX : + lookup(mdoc, line, la, tok, p); + + if (MDOC_MAX != c && -1 != c) { + if ( ! rew_elem(mdoc, tok)) + return(0); + return(mdoc_macro(mdoc, c, line, la, pos, buf)); + } else if (-1 == c) + return(0); + + if ( ! mdoc_word_alloc(mdoc, line, la, p)) + return(0); + mdoc->next = MDOC_NEXT_SIBLING; + } + + return(rew_elem(mdoc, tok)); +} + + +/* ARGSUSED */ +static int +obsolete(MACRO_PROT_ARGS) +{ + + return(pwarn(mdoc, line, ppos, WOBS)); +} + + +static int +phrase(struct mdoc *mdoc, int line, int ppos, char *buf) +{ + int i, la, c, quoted; + + /* + * Parse over words in a phrase. We have to handle this + * specially because we assume no calling context -- in normal + * circumstances, we switch argument parsing based on whether + * the parent macro accepts quotes, tabs, etc. Here, anything + * goes. + */ + + for (i = ppos; buf[i]; ) { + assert(' ' != buf[i]); + la = i; + quoted = 0; + + /* + * Read to next token. If quoted (check not escaped), + * scan ahead to next unescaped quote. If not quoted or + * escape-quoted, then scan ahead to next space. + */ + + if ((i && '\"' == buf[i] && '\\' != buf[i - 1]) || + (0 == i && '\"' == buf[i])) { + for (la = ++i; buf[i]; i++) + if ('\"' != buf[i]) + continue; + else if ('\\' != buf[i - 1]) + break; + if (0 == buf[i]) + return(perr(mdoc, line, la, EQUOT)); + quoted = 1; + } else + for ( ; buf[i]; i++) + if (i && ' ' == buf[i]) { + if ('\\' != buf[i - 1]) + break; + } else if (' ' == buf[i]) + break; + + /* If not end-of-line, terminate argument. */ + + if (buf[i]) + buf[i++] = 0; + + /* Read to next argument. */ + + for ( ; buf[i] && ' ' == buf[i]; i++) + /* Spin. */ ; + + /* + * If we're a non-quoted string, try to look up the + * value as a macro and execute it, if found. + */ + + c = quoted ? MDOC_MAX : + mdoc_hash_find(mdoc->htab, &buf[la]); + + if (MDOC_MAX != c) { + if ( ! mdoc_macro(mdoc, c, line, la, &i, buf)) + return(0); + return(append_delims(mdoc, line, &i, buf)); + } + + /* A regular word or quoted string. */ + + if ( ! mdoc_word_alloc(mdoc, line, la, &buf[la])) + return(0); + mdoc->next = MDOC_NEXT_SIBLING; + } + + return(1); +} diff --git a/usr.bin/mandoc/mdoc_strings.c b/usr.bin/mandoc/mdoc_strings.c new file mode 100644 index 00000000000..48b68fe7a99 --- /dev/null +++ b/usr.bin/mandoc/mdoc_strings.c @@ -0,0 +1,316 @@ +/* $Id: mdoc_strings.c,v 1.1 2009/04/06 20:30:40 kristaps Exp $ */ +/* + * Copyright (c) 2008 Kristaps Dzonsons <kristaps@kth.se> + * + * 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 <sys/types.h> + +#include <assert.h> +#include <ctype.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include "libmdoc.h" + +/* + * Various string-literal operations: converting scalars to and from + * strings, etc. + */ + +struct mdoc_secname { + const char *name; + int flag; +#define MSECNAME_META (1 << 0) +}; + +/* Section names corresponding to mdoc_sec. */ + +static const struct mdoc_secname secnames[] = { + { "PROLOGUE", MSECNAME_META }, + { "BODY", MSECNAME_META }, + { "NAME", 0 }, + { "LIBRARY", 0 }, + { "SYNOPSIS", 0 }, + { "DESCRIPTION", 0 }, + { "IMPLEMENTATION NOTES", 0 }, + { "RETURN VALUES", 0 }, + { "ENVIRONMENT", 0 }, + { "FILES", 0 }, + { "EXAMPLES", 0 }, + { "DIAGNOSTICS", 0 }, + { "COMPATIBILITY", 0 }, + { "ERRORS", 0 }, + { "SEE ALSO", 0 }, + { "STANDARDS", 0 }, + { "HISTORY", 0 }, + { "AUTHORS", 0 }, + { "CAVEATS", 0 }, + { "BUGS", 0 }, + { NULL, 0 } +}; + + +size_t +mdoc_isescape(const char *p) +{ + size_t c; + + if ('\\' != *p++) + return(0); + + switch (*p) { + case ('\\'): + /* FALLTHROUGH */ + case ('\''): + /* FALLTHROUGH */ + case ('`'): + /* FALLTHROUGH */ + case ('q'): + /* FALLTHROUGH */ + case ('-'): + /* FALLTHROUGH */ + case ('%'): + /* FALLTHROUGH */ + case ('0'): + /* FALLTHROUGH */ + case (' '): + /* FALLTHROUGH */ + case ('|'): + /* FALLTHROUGH */ + case ('&'): + /* FALLTHROUGH */ + case ('.'): + /* FALLTHROUGH */ + case (':'): + /* FALLTHROUGH */ + case ('e'): + return(2); + 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); +} + + +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('}'): + 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) +{ + const struct mdoc_secname *n; + int i; + + for (i = 0, n = secnames; n->name; n++, i++) + if ( ! (n->flag & MSECNAME_META)) + if (0 == strcmp(p, n->name)) + return((enum mdoc_sec)i); + + return(SEC_CUSTOM); +} + + +time_t +mdoc_atotime(const char *p) +{ + struct tm tm; + char *pp; + + (void)memset(&tm, 0, sizeof(struct tm)); + + if (0 == strcmp(p, "$Mdocdate: April 6 2009 $")) + return(time(NULL)); + if ((pp = strptime(p, "$Mdocdate: April 6 2009 $", &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); +} + + +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 00000000000..978bc273881 --- /dev/null +++ b/usr.bin/mandoc/mdoc_term.c @@ -0,0 +1,2238 @@ +/* $Id: mdoc_term.c,v 1.1 2009/04/06 20:30:40 kristaps Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@openbsd.org> + * + * 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 <sys/types.h> + +#include <assert.h> +#include <ctype.h> +#include <err.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "term.h" +#include "mdoc.h" + +/* FIXME: macro arguments can be escaped. */ +/* FIXME: support more offset/width tokens. */ + +#define TTYPE_PROG 0 +#define TTYPE_CMD_FLAG 1 +#define TTYPE_CMD_ARG 2 +#define TTYPE_SECTION 3 +#define TTYPE_FUNC_DECL 4 +#define TTYPE_VAR_DECL 5 +#define TTYPE_FUNC_TYPE 6 +#define TTYPE_FUNC_NAME 7 +#define TTYPE_FUNC_ARG 8 +#define TTYPE_LINK 9 +#define TTYPE_SSECTION 10 +#define TTYPE_FILE 11 +#define TTYPE_EMPH 12 +#define TTYPE_CONFIG 13 +#define TTYPE_CMD 14 +#define TTYPE_INCLUDE 15 +#define TTYPE_SYMB 16 +#define TTYPE_SYMBOL 17 +#define TTYPE_DIAG 18 +#define TTYPE_LINK_ANCHOR 19 +#define TTYPE_LINK_TEXT 20 +#define TTYPE_REF_JOURNAL 21 +#define TTYPE_LIST 22 +#define TTYPE_NMAX 23 + +const int ttypes[TTYPE_NMAX] = { + TERMP_BOLD, /* TTYPE_PROG */ + TERMP_BOLD, /* TTYPE_CMD_FLAG */ + TERMP_UNDER, /* TTYPE_CMD_ARG */ + TERMP_BOLD, /* TTYPE_SECTION */ + TERMP_BOLD, /* TTYPE_FUNC_DECL */ + TERMP_UNDER, /* TTYPE_VAR_DECL */ + TERMP_UNDER, /* TTYPE_FUNC_TYPE */ + TERMP_BOLD, /* TTYPE_FUNC_NAME */ + TERMP_UNDER, /* TTYPE_FUNC_ARG */ + TERMP_UNDER, /* TTYPE_LINK */ + TERMP_BOLD, /* TTYPE_SSECTION */ + TERMP_UNDER, /* TTYPE_FILE */ + TERMP_UNDER, /* TTYPE_EMPH */ + TERMP_BOLD, /* TTYPE_CONFIG */ + TERMP_BOLD, /* TTYPE_CMD */ + TERMP_BOLD, /* TTYPE_INCLUDE */ + TERMP_BOLD, /* TTYPE_SYMB */ + TERMP_BOLD, /* TTYPE_SYMBOL */ + TERMP_BOLD, /* TTYPE_DIAG */ + TERMP_UNDER, /* TTYPE_LINK_ANCHOR */ + TERMP_BOLD, /* TTYPE_LINK_TEXT */ + TERMP_UNDER, /* TTYPE_REF_JOURNAL */ + TERMP_BOLD /* TTYPE_LIST */ +}; + +/* XXX - clean this up. */ + +struct termpair { + struct termpair *ppair; + int type; +#define TERMPAIR_FLAG (1 << 0) + int flag; + size_t offset; + size_t rmargin; + int count; +}; + +#define TERMPAIR_SETFLAG(termp, p, fl) \ + do { \ + assert(! (TERMPAIR_FLAG & (p)->type)); \ + (termp)->flags |= (fl); \ + (p)->flag = (fl); \ + (p)->type |= TERMPAIR_FLAG; \ + } while ( /* CONSTCOND */ 0) + +#define DECL_ARGS \ + struct termp *p, struct termpair *pair, \ + const struct mdoc_meta *meta, \ + const struct mdoc_node *node + +#define DECL_PRE(name) \ +static int name##_pre(DECL_ARGS) +#define DECL_POST(name) \ +static void name##_post(DECL_ARGS) +#define DECL_PREPOST(name) \ +DECL_PRE(name); \ +DECL_POST(name); + +DECL_PREPOST(termp__t); +DECL_PREPOST(termp_aq); +DECL_PREPOST(termp_bd); +DECL_PREPOST(termp_bq); +DECL_PREPOST(termp_brq); +DECL_PREPOST(termp_d1); +DECL_PREPOST(termp_dq); +DECL_PREPOST(termp_fd); +DECL_PREPOST(termp_fn); +DECL_PREPOST(termp_fo); +DECL_PREPOST(termp_ft); +DECL_PREPOST(termp_in); +DECL_PREPOST(termp_it); +DECL_PREPOST(termp_lb); +DECL_PREPOST(termp_op); +DECL_PREPOST(termp_pf); +DECL_PREPOST(termp_pq); +DECL_PREPOST(termp_qq); +DECL_PREPOST(termp_sh); +DECL_PREPOST(termp_ss); +DECL_PREPOST(termp_sq); +DECL_PREPOST(termp_vt); + +DECL_PRE(termp__j); +DECL_PRE(termp_ap); +DECL_PRE(termp_ar); +DECL_PRE(termp_at); +DECL_PRE(termp_bf); +DECL_PRE(termp_bsx); +DECL_PRE(termp_bt); +DECL_PRE(termp_cd); +DECL_PRE(termp_cm); +DECL_PRE(termp_dx); +DECL_PRE(termp_em); +DECL_PRE(termp_ex); +DECL_PRE(termp_fa); +DECL_PRE(termp_fl); +DECL_PRE(termp_fx); +DECL_PRE(termp_ic); +DECL_PRE(termp_lk); +DECL_PRE(termp_ms); +DECL_PRE(termp_mt); +DECL_PRE(termp_nd); +DECL_PRE(termp_nm); +DECL_PRE(termp_ns); +DECL_PRE(termp_nx); +DECL_PRE(termp_ox); +DECL_PRE(termp_pa); +DECL_PRE(termp_pp); +DECL_PRE(termp_rs); +DECL_PRE(termp_rv); +DECL_PRE(termp_sm); +DECL_PRE(termp_st); +DECL_PRE(termp_sx); +DECL_PRE(termp_sy); +DECL_PRE(termp_ud); +DECL_PRE(termp_ux); +DECL_PRE(termp_va); +DECL_PRE(termp_xr); + +DECL_POST(termp___); +DECL_POST(termp_bl); +DECL_POST(termp_bx); + +struct termact { + int (*pre)(DECL_ARGS); + void (*post)(DECL_ARGS); +}; + +static const struct termact termacts[MDOC_MAX] = { + { NULL, NULL }, /* \" */ + { NULL, NULL }, /* Dd */ + { NULL, NULL }, /* Dt */ + { NULL, NULL }, /* Os */ + { termp_sh_pre, termp_sh_post }, /* Sh */ + { termp_ss_pre, termp_ss_post }, /* Ss */ + { termp_pp_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 */ + { NULL, NULL }, /* An */ + { termp_ar_pre, NULL }, /* Ar */ + { termp_cd_pre, NULL }, /* Cd */ + { termp_cm_pre, NULL }, /* Cm */ + { NULL, NULL }, /* Dv */ + { NULL, NULL }, /* Er */ + { NULL, NULL }, /* Ev */ + { termp_ex_pre, NULL }, /* Ex */ + { termp_fa_pre, NULL }, /* Fa */ + { termp_fd_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_ic_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_pa_pre, NULL }, /* Pa */ + { termp_rv_pre, NULL }, /* Rv */ + { termp_st_pre, NULL }, /* St */ + { termp_va_pre, NULL }, /* Va */ + { termp_vt_pre, termp_vt_post }, /* Vt */ + { termp_xr_pre, NULL }, /* Xr */ + { NULL, termp____post }, /* %A */ + { NULL, termp____post }, /* %B */ + { NULL, termp____post }, /* %D */ + { NULL, termp____post }, /* %I */ + { termp__j_pre, termp____post }, /* %J */ + { NULL, termp____post }, /* %N */ + { NULL, termp____post }, /* %O */ + { NULL, termp____post }, /* %P */ + { NULL, termp____post }, /* %R */ + { termp__t_pre, termp__t_post }, /* %T */ + { NULL, termp____post }, /* %V */ + { NULL, NULL }, /* Ac */ + { termp_aq_pre, termp_aq_post }, /* Ao */ + { termp_aq_pre, termp_aq_post }, /* Aq */ + { termp_at_pre, 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_bsx_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_em_pre, NULL }, /* Em */ + { NULL, NULL }, /* Eo */ + { termp_fx_pre, NULL }, /* Fx */ + { termp_ms_pre, NULL }, /* Ms */ + { NULL, NULL }, /* No */ + { termp_ns_pre, NULL }, /* Ns */ + { termp_nx_pre, NULL }, /* Nx */ + { termp_ox_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_sx_pre, NULL }, /* Sx */ + { termp_sy_pre, NULL }, /* Sy */ + { NULL, NULL }, /* Tn */ + { termp_ux_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 */ + { termp_lb_pre, termp_lb_post }, /* Lb */ + { termp_ap_pre, NULL }, /* Lb */ + { termp_pp_pre, NULL }, /* Pp */ + { termp_lk_pre, NULL }, /* Lk */ + { termp_mt_pre, NULL }, /* Mt */ + { termp_brq_pre, termp_brq_post }, /* Brq */ + { termp_brq_pre, termp_brq_post }, /* Bro */ + { NULL, NULL }, /* Brc */ + { NULL, NULL }, /* %C */ + { NULL, NULL }, /* Es */ + { NULL, NULL }, /* En */ + { termp_dx_pre, NULL }, /* Dx */ + { NULL, NULL }, /* %Q */ +}; + +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 size_t arg_offset(const struct mdoc_argv *); +static size_t arg_width(const struct mdoc_argv *, int); +static int arg_listtype(const struct mdoc_node *); +static int fmt_block_vspace(struct termp *, + const struct mdoc_node *, + const struct mdoc_node *); +static void print_node(DECL_ARGS); +static void print_head(struct termp *, + const struct mdoc_meta *); +static void print_body(DECL_ARGS); +static void print_foot(struct termp *, + const struct mdoc_meta *); +static void sanity(const struct mdoc_node *); + + +int +mdoc_run(struct termp *p, const struct mdoc *m) +{ + + print_head(p, mdoc_meta(m)); + print_body(p, NULL, mdoc_meta(m), mdoc_node(m)); + print_foot(p, mdoc_meta(m)); + return(1); +} + + +static void +print_body(DECL_ARGS) +{ + + print_node(p, pair, meta, node); + if ( ! node->next) + return; + print_body(p, pair, meta, node->next); +} + + +static void +print_node(DECL_ARGS) +{ + int dochild; + struct termpair npair; + + /* Some quick sanity-checking. */ + + sanity(node); + + /* Pre-processing. */ + + dochild = 1; + npair.ppair = pair; + npair.type = 0; + npair.offset = npair.rmargin = 0; + npair.flag = 0; + npair.count = 0; + + if (MDOC_TEXT != node->type) { + if (termacts[node->tok].pre) + if ( ! (*termacts[node->tok].pre)(p, &npair, meta, node)) + dochild = 0; + } else /* MDOC_TEXT == node->type */ + term_word(p, node->string); + + /* Children. */ + + if (TERMPAIR_FLAG & npair.type) + p->flags |= npair.flag; + + if (dochild && node->child) + print_body(p, &npair, meta, node->child); + + if (TERMPAIR_FLAG & npair.type) + p->flags &= ~npair.flag; + + /* Post-processing. */ + + if (MDOC_TEXT != node->type) + if (termacts[node->tok].post) + (*termacts[node->tok].post)(p, &npair, meta, node); +} + + +static void +print_foot(struct termp *p, const struct mdoc_meta *meta) +{ + struct tm *tm; + char *buf, *os; + + if (NULL == (buf = malloc(p->rmargin))) + err(1, "malloc"); + if (NULL == (os = malloc(p->rmargin))) + err(1, "malloc"); + + tm = localtime(&meta->date); + + if (NULL == strftime(buf, p->rmargin, "%B %d, %Y", tm)) + err(1, "strftime"); + + (void)strlcpy(os, meta->os, p->rmargin); + + /* + * This is /slightly/ different from regular groff output + * because we don't have page numbers. Print the following: + * + * OS MDOCDATE + */ + + term_vspace(p); + + p->flags |= TERMP_NOSPACE | TERMP_NOBREAK; + p->rmargin = p->maxrmargin - strlen(buf); + p->offset = 0; + + term_word(p, os); + 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); + + free(buf); + free(os); +} + + +static void +print_head(struct termp *p, const struct mdoc_meta *meta) +{ + char *buf, *title; + + p->rmargin = p->maxrmargin; + p->offset = 0; + + if (NULL == (buf = malloc(p->rmargin))) + err(1, "malloc"); + if (NULL == (title = malloc(p->rmargin))) + err(1, "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(meta->vol); + (void)strlcpy(buf, meta->vol, p->rmargin); + + if (meta->arch) { + (void)strlcat(buf, " (", p->rmargin); + (void)strlcat(buf, meta->arch, p->rmargin); + (void)strlcat(buf, ")", p->rmargin); + } + + (void)snprintf(title, p->rmargin, "%s(%d)", + meta->title, meta->msec); + + p->offset = 0; + p->rmargin = (p->maxrmargin - strlen(buf)) / 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); +} + + +static void +sanity(const struct mdoc_node *n) +{ + char *p; + + p = "regular form violated"; + + switch (n->type) { + case (MDOC_TEXT): + if (n->child) + errx(1, p); + if (NULL == n->parent) + errx(1, p); + if (NULL == n->string) + errx(1, p); + switch (n->parent->type) { + case (MDOC_TEXT): + /* FALLTHROUGH */ + case (MDOC_ROOT): + errx(1, p); + /* NOTREACHED */ + default: + break; + } + break; + case (MDOC_ELEM): + if (NULL == n->parent) + errx(1, p); + switch (n->parent->type) { + case (MDOC_TAIL): + /* FALLTHROUGH */ + case (MDOC_BODY): + /* FALLTHROUGH */ + case (MDOC_HEAD): + break; + default: + errx(1, p); + /* NOTREACHED */ + } + if (n->child) switch (n->child->type) { + case (MDOC_TEXT): + break; + default: + errx(1, p); + /* NOTREACHED */ + } + break; + case (MDOC_HEAD): + /* FALLTHROUGH */ + case (MDOC_BODY): + /* FALLTHROUGH */ + case (MDOC_TAIL): + if (NULL == n->parent) + errx(1, p); + if (MDOC_BLOCK != n->parent->type) + errx(1, p); + if (n->child) switch (n->child->type) { + case (MDOC_BLOCK): + /* FALLTHROUGH */ + case (MDOC_ELEM): + /* FALLTHROUGH */ + case (MDOC_TEXT): + break; + default: + errx(1, p); + /* NOTREACHED */ + } + break; + case (MDOC_BLOCK): + if (NULL == n->parent) + errx(1, p); + if (NULL == n->child) + errx(1, p); + switch (n->parent->type) { + case (MDOC_ROOT): + /* FALLTHROUGH */ + case (MDOC_HEAD): + /* FALLTHROUGH */ + case (MDOC_BODY): + /* FALLTHROUGH */ + case (MDOC_TAIL): + break; + default: + errx(1, p); + /* NOTREACHED */ + } + switch (n->child->type) { + case (MDOC_ROOT): + /* FALLTHROUGH */ + case (MDOC_ELEM): + errx(1, p); + /* NOTREACHED */ + default: + break; + } + break; + case (MDOC_ROOT): + if (n->parent) + errx(1, p); + if (NULL == n->child) + errx(1, p); + switch (n->child->type) { + case (MDOC_BLOCK): + break; + default: + errx(1, p); + /* NOTREACHED */ + } + break; + } +} + + +static size_t +arg_width(const struct mdoc_argv *arg, int pos) +{ + size_t v; + int i, len; + + assert(pos < (int)arg->sz && pos >= 0); + assert(arg->value[pos]); + if (0 == strcmp(arg->value[pos], "indent")) + return(INDENT); + if (0 == strcmp(arg->value[pos], "indent-two")) + return(INDENT * 2); + + if (0 == (len = (int)strlen(arg->value[pos]))) + return(0); + + for (i = 0; i < len - 1; i++) + if ( ! isdigit((u_char)arg->value[pos][i])) + break; + + if (i == len - 1) { + if ('n' == arg->value[pos][len - 1]) { + v = (size_t)atoi(arg->value[pos]); + return(v); + } + + } + return(strlen(arg->value[pos]) + 1); +} + + +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_Ohang): + return(n->args->argv[i].arg); + default: + break; + } + + errx(1, "list type not supported"); + /* NOTREACHED */ +} + + +static size_t +arg_offset(const struct mdoc_argv *arg) +{ + + assert(*arg->value); + if (0 == strcmp(*arg->value, "indent")) + return(INDENT); + if (0 == strcmp(*arg->value, "indent-two")) + return(INDENT * 2); + return(strlen(*arg->value)); +} + + +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); +} + + +/* ARGSUSED */ +static int +fmt_block_vspace(struct termp *p, + const struct mdoc_node *bl, + const struct mdoc_node *node) +{ + const struct mdoc_node *n; + + term_newln(p); + + if (arg_hasattr(MDOC_Compact, bl)) + return(1); + + for (n = node; n; n = n->parent) { + if (MDOC_BLOCK != n->type) + continue; + if (MDOC_Ss == n->tok) + break; + if (MDOC_Sh == n->tok) + break; + if (NULL == n->prev) + continue; + term_vspace(p); + break; + } + + return(1); +} + + +/* ARGSUSED */ +static int +termp_dq_pre(DECL_ARGS) +{ + + if (MDOC_BODY != node->type) + return(1); + + term_word(p, "\\(lq"); + p->flags |= TERMP_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static void +termp_dq_post(DECL_ARGS) +{ + + if (MDOC_BODY != node->type) + return; + + p->flags |= TERMP_NOSPACE; + term_word(p, "\\(rq"); +} + + +/* ARGSUSED */ +static int +termp_it_pre(DECL_ARGS) +{ + const struct mdoc_node *bl, *n; + char buf[7]; + int i, type, keys[3], vals[3]; + size_t width, offset; + + if (MDOC_BLOCK == node->type) + return(fmt_block_vspace(p, node->parent->parent, node)); + + bl = node->parent->parent->parent; + + /* Save parent attributes. */ + + pair->offset = p->offset; + pair->rmargin = p->rmargin; + 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); + + /* Calculate real width and offset. */ + + switch (type) { + case (MDOC_Column): + if (MDOC_BODY == node->type) + break; + for (i = 0, n = node->prev; n; n = n->prev, i++) + offset += arg_width + (&bl->args->argv[vals[2]], i); + assert(i < (int)bl->args->argv[vals[2]].sz); + width = arg_width(&bl->args->argv[vals[2]], i); + if (vals[1] >= 0) + offset += arg_offset(&bl->args->argv[vals[1]]); + break; + default: + if (vals[0] >= 0) + width = arg_width(&bl->args->argv[vals[0]], 0); + if (vals[1] >= 0) + offset = arg_offset(&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_Enum): + /* FALLTHROUGH */ + case (MDOC_Hyphen): + if (width < 4) + width = 4; + break; + case (MDOC_Tag): + if (0 == width) + width = 10; + break; + default: + break; + } + + /* + * Whitespace control. Inset bodies need an initial space. + */ + + switch (type) { + case (MDOC_Diag): + /* FALLTHROUGH */ + case (MDOC_Inset): + if (MDOC_BODY == node->type) + p->flags &= ~TERMP_NOSPACE; + else + p->flags |= TERMP_NOSPACE; + break; + default: + p->flags |= TERMP_NOSPACE; + break; + } + + /* + * Style flags. Diagnostic heads need TTYPE_DIAG. + */ + + switch (type) { + case (MDOC_Diag): + if (MDOC_HEAD == node->type) + p->flags |= ttypes[TTYPE_DIAG]; + 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): + /* FALLTHROUGH */ + case (MDOC_Tag): + if (MDOC_HEAD == node->type) + p->flags |= TERMP_NOBREAK; + else + p->flags |= TERMP_NOLPAD; + if (MDOC_HEAD == node->type && MDOC_Tag == type) + if (NULL == node->next || + NULL == node->next->child) + p->flags |= TERMP_NONOBREAK; + break; + case (MDOC_Column): + if (MDOC_HEAD == node->type) { + assert(node->next); + if (MDOC_BODY == node->next->type) + p->flags &= ~TERMP_NOBREAK; + else + p->flags |= TERMP_NOBREAK; + if (node->prev) + p->flags |= TERMP_NOLPAD; + } + break; + case (MDOC_Diag): + if (MDOC_HEAD == node->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_Bullet): + /* FALLTHROUGH */ + case (MDOC_Dash): + /* FALLTHROUGH */ + case (MDOC_Enum): + /* FALLTHROUGH */ + case (MDOC_Hyphen): + /* FALLTHROUGH */ + case (MDOC_Tag): + if (MDOC_HEAD == node->type) + p->rmargin = p->offset + width; + else + p->offset += width; + break; + case (MDOC_Column): + p->rmargin = p->offset + width; + break; + default: + break; + } + + /* + * The dash, hyphen, bullet and enum lists all have a special + * HEAD character. Print it now. + */ + + if (MDOC_HEAD == node->type) + switch (type) { + case (MDOC_Bullet): + term_word(p, "\\[bu]"); + break; + case (MDOC_Dash): + /* FALLTHROUGH */ + case (MDOC_Hyphen): + term_word(p, "\\-"); + 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 == node->type) + return(0); + break; + case (MDOC_Column): + if (MDOC_BODY == node->type) + return(0); + break; + default: + break; + } + + return(1); +} + + +/* ARGSUSED */ +static void +termp_it_post(DECL_ARGS) +{ + int type; + + if (MDOC_BODY != node->type && MDOC_HEAD != node->type) + return; + + type = arg_listtype(node->parent->parent->parent); + + switch (type) { + case (MDOC_Diag): + /* FALLTHROUGH */ + case (MDOC_Item): + /* FALLTHROUGH */ + case (MDOC_Inset): + if (MDOC_BODY == node->type) + term_flushln(p); + break; + case (MDOC_Column): + if (MDOC_HEAD == node->type) + term_flushln(p); + break; + default: + term_flushln(p); + break; + } + + p->offset = pair->offset; + p->rmargin = pair->rmargin; + p->flags = pair->flag; +} + + +/* ARGSUSED */ +static int +termp_nm_pre(DECL_ARGS) +{ + + if (SEC_SYNOPSIS == node->sec) + term_newln(p); + + TERMPAIR_SETFLAG(p, pair, ttypes[TTYPE_PROG]); + if (NULL == node->child) + term_word(p, meta->name); + + return(1); +} + + +/* ARGSUSED */ +static int +termp_fl_pre(DECL_ARGS) +{ + + TERMPAIR_SETFLAG(p, pair, ttypes[TTYPE_CMD_FLAG]); + term_word(p, "\\-"); + p->flags |= TERMP_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static int +termp_ar_pre(DECL_ARGS) +{ + + TERMPAIR_SETFLAG(p, pair, ttypes[TTYPE_CMD_ARG]); + return(1); +} + + +/* ARGSUSED */ +static int +termp_ns_pre(DECL_ARGS) +{ + + p->flags |= TERMP_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static int +termp_pp_pre(DECL_ARGS) +{ + + term_vspace(p); + return(1); +} + + +/* ARGSUSED */ +static int +termp_st_pre(DECL_ARGS) +{ + const char *cp; + + if (node->child && (cp = mdoc_a2st(node->child->string))) + term_word(p, cp); + return(0); +} + + +/* ARGSUSED */ +static int +termp_rs_pre(DECL_ARGS) +{ + + if (MDOC_BLOCK == node->type && node->prev) + term_vspace(p); + return(1); +} + + +/* ARGSUSED */ +static int +termp_rv_pre(DECL_ARGS) +{ + int i; + + if (-1 == (i = arg_getattr(MDOC_Std, node))) + errx(1, "expected -std argument"); + if (1 != node->args->argv[i].sz) + errx(1, "expected -std argument"); + + term_newln(p); + term_word(p, "The"); + + p->flags |= ttypes[TTYPE_FUNC_NAME]; + term_word(p, *node->args->argv[i].value); + p->flags &= ~ttypes[TTYPE_FUNC_NAME]; + p->flags |= TERMP_NOSPACE; + + term_word(p, "() function returns the value 0 if successful;"); + term_word(p, "otherwise the value -1 is returned and the"); + term_word(p, "global variable"); + + p->flags |= ttypes[TTYPE_VAR_DECL]; + term_word(p, "errno"); + p->flags &= ~ttypes[TTYPE_VAR_DECL]; + + term_word(p, "is set to indicate the error."); + + return(1); +} + + +/* ARGSUSED */ +static int +termp_ex_pre(DECL_ARGS) +{ + int i; + + if (-1 == (i = arg_getattr(MDOC_Std, node))) + errx(1, "expected -std argument"); + if (1 != node->args->argv[i].sz) + errx(1, "expected -std argument"); + + term_word(p, "The"); + p->flags |= ttypes[TTYPE_PROG]; + term_word(p, *node->args->argv[i].value); + p->flags &= ~ttypes[TTYPE_PROG]; + term_word(p, "utility exits 0 on success, and >0 if an error occurs."); + + return(1); +} + + +/* ARGSUSED */ +static int +termp_nd_pre(DECL_ARGS) +{ + + term_word(p, "\\-"); + return(1); +} + + +/* ARGSUSED */ +static void +termp_bl_post(DECL_ARGS) +{ + + if (MDOC_BLOCK == node->type) + term_newln(p); +} + + +/* ARGSUSED */ +static void +termp_op_post(DECL_ARGS) +{ + + if (MDOC_BODY != node->type) + return; + p->flags |= TERMP_NOSPACE; + term_word(p, "\\(rB"); +} + + +/* ARGSUSED */ +static int +termp_xr_pre(DECL_ARGS) +{ + const struct mdoc_node *n; + + if (NULL == (n = node->child)) + errx(1, "expected text line argument"); + term_word(p, n->string); + if (NULL == (n = n->next)) + return(0); + p->flags |= TERMP_NOSPACE; + term_word(p, "("); + p->flags |= TERMP_NOSPACE; + term_word(p, n->string); + p->flags |= TERMP_NOSPACE; + term_word(p, ")"); + return(0); +} + + +/* ARGSUSED */ +static int +termp_vt_pre(DECL_ARGS) +{ + + /* FIXME: this can be "type name". */ + TERMPAIR_SETFLAG(p, pair, ttypes[TTYPE_VAR_DECL]); + return(1); +} + + +/* ARGSUSED */ +static void +termp_vt_post(DECL_ARGS) +{ + + if (node->sec == SEC_SYNOPSIS) + term_vspace(p); +} + + +/* ARGSUSED */ +static int +termp_fd_pre(DECL_ARGS) +{ + + /* + * FIXME: this naming is bad. This value is used, in general, + * for the #include header or other preprocessor statement. + */ + TERMPAIR_SETFLAG(p, pair, ttypes[TTYPE_FUNC_DECL]); + return(1); +} + + +/* ARGSUSED */ +static void +termp_fd_post(DECL_ARGS) +{ + + if (node->sec != SEC_SYNOPSIS) + return; + term_newln(p); + if (node->next && MDOC_Fd != node->next->tok) + term_vspace(p); +} + + +/* ARGSUSED */ +static int +termp_sh_pre(DECL_ARGS) +{ + + switch (node->type) { + case (MDOC_HEAD): + term_vspace(p); + TERMPAIR_SETFLAG(p, pair, ttypes[TTYPE_SECTION]); + break; + case (MDOC_BODY): + p->offset = INDENT; + break; + default: + break; + } + return(1); +} + + +/* ARGSUSED */ +static void +termp_sh_post(DECL_ARGS) +{ + + switch (node->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 (node->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(1); +} + + +/* ARGSUSED */ +static int +termp_lb_pre(DECL_ARGS) +{ + const char *lb; + + if (NULL == node->child) + errx(1, "expected text line argument"); + if ((lb = mdoc_a2lib(node->child->string))) { + term_word(p, lb); + return(0); + } + term_word(p, "library"); + return(1); +} + + +/* ARGSUSED */ +static void +termp_lb_post(DECL_ARGS) +{ + + term_newln(p); +} + + +/* ARGSUSED */ +static int +termp_ud_pre(DECL_ARGS) +{ + + term_word(p, "currently under development."); + return(1); +} + + +/* ARGSUSED */ +static int +termp_d1_pre(DECL_ARGS) +{ + + if (MDOC_BLOCK != node->type) + return(1); + term_newln(p); + p->offset += (pair->offset = INDENT); + return(1); +} + + +/* ARGSUSED */ +static void +termp_d1_post(DECL_ARGS) +{ + + if (MDOC_BLOCK != node->type) + return; + term_newln(p); + p->offset -= pair->offset; +} + + +/* ARGSUSED */ +static int +termp_aq_pre(DECL_ARGS) +{ + + if (MDOC_BODY != node->type) + return(1); + term_word(p, "\\(la"); + p->flags |= TERMP_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static void +termp_aq_post(DECL_ARGS) +{ + + if (MDOC_BODY != node->type) + return; + p->flags |= TERMP_NOSPACE; + term_word(p, "\\(ra"); +} + + +/* ARGSUSED */ +static int +termp_ft_pre(DECL_ARGS) +{ + + if (SEC_SYNOPSIS == node->sec) + if (node->prev && MDOC_Fo == node->prev->tok) + term_vspace(p); + TERMPAIR_SETFLAG(p, pair, ttypes[TTYPE_FUNC_TYPE]); + return(1); +} + + +/* ARGSUSED */ +static void +termp_ft_post(DECL_ARGS) +{ + + if (SEC_SYNOPSIS == node->sec) + term_newln(p); +} + + +/* ARGSUSED */ +static int +termp_fn_pre(DECL_ARGS) +{ + const struct mdoc_node *n; + + if (NULL == node->child) + errx(1, "expected text line arguments"); + + /* FIXME: can be "type funcname" "type varname"... */ + + p->flags |= ttypes[TTYPE_FUNC_NAME]; + term_word(p, node->child->string); + p->flags &= ~ttypes[TTYPE_FUNC_NAME]; + + p->flags |= TERMP_NOSPACE; + term_word(p, "("); + + for (n = node->child->next; n; n = n->next) { + p->flags |= ttypes[TTYPE_FUNC_ARG]; + term_word(p, n->string); + p->flags &= ~ttypes[TTYPE_FUNC_ARG]; + if (n->next) + term_word(p, ","); + } + + term_word(p, ")"); + + if (SEC_SYNOPSIS == node->sec) + term_word(p, ";"); + + return(0); +} + + +/* ARGSUSED */ +static void +termp_fn_post(DECL_ARGS) +{ + + if (node->sec == SEC_SYNOPSIS && node->next) + term_vspace(p); + +} + + +/* ARGSUSED */ +static int +termp_sx_pre(DECL_ARGS) +{ + + TERMPAIR_SETFLAG(p, pair, ttypes[TTYPE_LINK]); + return(1); +} + + +/* ARGSUSED */ +static int +termp_fa_pre(DECL_ARGS) +{ + struct mdoc_node *n; + + if (node->parent->tok != MDOC_Fo) { + TERMPAIR_SETFLAG(p, pair, ttypes[TTYPE_FUNC_ARG]); + return(1); + } + + for (n = node->child; n; n = n->next) { + p->flags |= ttypes[TTYPE_FUNC_ARG]; + term_word(p, n->string); + p->flags &= ~ttypes[TTYPE_FUNC_ARG]; + if (n->next) + term_word(p, ","); + } + + if (node->child && node->next && node->next->tok == MDOC_Fa) + term_word(p, ","); + + return(0); +} + + +/* ARGSUSED */ +static int +termp_va_pre(DECL_ARGS) +{ + + TERMPAIR_SETFLAG(p, pair, ttypes[TTYPE_VAR_DECL]); + return(1); +} + + +/* ARGSUSED */ +static int +termp_bd_pre(DECL_ARGS) +{ + int i, type, ln; + + /* + * This is fairly tricky due primarily to crappy documentation. + * If -ragged or -filled are specified, the block does nothing + * but change the indentation. + * + * If, on the other hand, -unfilled or -literal are specified, + * then the game changes. Text is printed exactly as entered in + * the display: if a macro line, a newline is appended to the + * line. Blank lines are allowed. + */ + + if (MDOC_BLOCK == node->type) + return(fmt_block_vspace(p, node, node)); + else if (MDOC_BODY != node->type) + return(1); + + if (NULL == node->parent->args) + errx(1, "missing display type"); + + pair->offset = p->offset; + + for (type = -1, i = 0; + i < (int)node->parent->args->argc; i++) { + switch (node->parent->args->argv[i].arg) { + case (MDOC_Ragged): + /* FALLTHROUGH */ + case (MDOC_Filled): + /* FALLTHROUGH */ + case (MDOC_Unfilled): + /* FALLTHROUGH */ + case (MDOC_Literal): + type = node->parent->args->argv[i].arg; + i = (int)node->parent->args->argc; + break; + default: + break; + } + } + + if (NULL == node->parent->args) + errx(1, "missing display type"); + + i = arg_getattr(MDOC_Offset, node->parent); + if (-1 != i) { + if (1 != node->parent->args->argv[i].sz) + errx(1, "expected single value"); + p->offset += arg_offset(&node->parent->args->argv[i]); + } + + switch (type) { + case (MDOC_Literal): + /* FALLTHROUGH */ + case (MDOC_Unfilled): + break; + default: + return(1); + } + + /* + * Tricky. Iterate through all children. If we're on a + * different parse line, append a newline and then the contents. + * Ew. + */ + + p->flags |= TERMP_LITERAL; + ln = node->child ? node->child->line : 0; + + for (node = node->child; node; node = node->next) { + if (ln < node->line) { + term_flushln(p); + p->flags |= TERMP_NOSPACE; + } + ln = node->line; + print_node(p, pair, meta, node); + } + + return(0); +} + + +/* ARGSUSED */ +static void +termp_bd_post(DECL_ARGS) +{ + + if (MDOC_BODY != node->type) + return; + + term_flushln(p); + p->flags &= ~TERMP_LITERAL; + p->offset = pair->offset; + p->flags |= TERMP_NOSPACE; +} + + +/* ARGSUSED */ +static int +termp_qq_pre(DECL_ARGS) +{ + + if (MDOC_BODY != node->type) + return(1); + term_word(p, "\""); + p->flags |= TERMP_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static void +termp_qq_post(DECL_ARGS) +{ + + if (MDOC_BODY != node->type) + return; + p->flags |= TERMP_NOSPACE; + term_word(p, "\""); +} + + +/* ARGSUSED */ +static int +termp_bsx_pre(DECL_ARGS) +{ + + term_word(p, "BSDI BSD/OS"); + return(1); +} + + +/* ARGSUSED */ +static void +termp_bx_post(DECL_ARGS) +{ + + if (node->child) + p->flags |= TERMP_NOSPACE; + term_word(p, "BSD"); +} + + +/* FIXME: consolidate the following into termp_system. */ + + +/* ARGSUSED */ +static int +termp_ox_pre(DECL_ARGS) +{ + + term_word(p, "OpenBSD"); + return(1); +} + + +/* ARGSUSED */ +static int +termp_dx_pre(DECL_ARGS) +{ + + term_word(p, "DragonFly"); + return(1); +} + + +/* ARGSUSED */ +static int +termp_ux_pre(DECL_ARGS) +{ + + term_word(p, "UNIX"); + return(1); +} + + +/* ARGSUSED */ +static int +termp_fx_pre(DECL_ARGS) +{ + + term_word(p, "FreeBSD"); + return(1); +} + + +/* ARGSUSED */ +static int +termp_nx_pre(DECL_ARGS) +{ + + term_word(p, "NetBSD"); + return(1); +} + + +/* ARGSUSED */ +static int +termp_sq_pre(DECL_ARGS) +{ + + if (MDOC_BODY != node->type) + return(1); + term_word(p, "\\(oq"); + p->flags |= TERMP_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static void +termp_sq_post(DECL_ARGS) +{ + + if (MDOC_BODY != node->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 (node->type) { + case (MDOC_BLOCK): + term_newln(p); + if (node->prev) + term_vspace(p); + break; + case (MDOC_HEAD): + TERMPAIR_SETFLAG(p, pair, ttypes[TTYPE_SSECTION]); + p->offset = INDENT / 2; + break; + default: + break; + } + + return(1); +} + + +/* ARGSUSED */ +static void +termp_ss_post(DECL_ARGS) +{ + + switch (node->type) { + case (MDOC_HEAD): + term_newln(p); + p->offset = INDENT; + break; + default: + break; + } +} + + +/* ARGSUSED */ +static int +termp_pa_pre(DECL_ARGS) +{ + + TERMPAIR_SETFLAG(p, pair, ttypes[TTYPE_FILE]); + return(1); +} + + +/* ARGSUSED */ +static int +termp_em_pre(DECL_ARGS) +{ + + TERMPAIR_SETFLAG(p, pair, ttypes[TTYPE_EMPH]); + return(1); +} + + +/* ARGSUSED */ +static int +termp_cd_pre(DECL_ARGS) +{ + + TERMPAIR_SETFLAG(p, pair, ttypes[TTYPE_CONFIG]); + term_newln(p); + return(1); +} + + +/* ARGSUSED */ +static int +termp_cm_pre(DECL_ARGS) +{ + + TERMPAIR_SETFLAG(p, pair, ttypes[TTYPE_CMD_FLAG]); + return(1); +} + + +/* ARGSUSED */ +static int +termp_ic_pre(DECL_ARGS) +{ + + TERMPAIR_SETFLAG(p, pair, ttypes[TTYPE_CMD]); + return(1); +} + + +/* ARGSUSED */ +static int +termp_in_pre(DECL_ARGS) +{ + + TERMPAIR_SETFLAG(p, pair, ttypes[TTYPE_INCLUDE]); + term_word(p, "#include"); + term_word(p, "<"); + p->flags |= TERMP_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static void +termp_in_post(DECL_ARGS) +{ + + p->flags |= TERMP_NOSPACE; + term_word(p, ">"); + + term_newln(p); + if (SEC_SYNOPSIS != node->sec) + return; + if (node->next && MDOC_In != node->next->tok) + term_vspace(p); +} + + +/* ARGSUSED */ +static int +termp_at_pre(DECL_ARGS) +{ + const char *att; + + att = NULL; + + if (node->child) + att = mdoc_a2att(node->child->string); + if (NULL == att) + att = "AT&T UNIX"; + + term_word(p, att); + return(0); +} + + +/* ARGSUSED */ +static int +termp_brq_pre(DECL_ARGS) +{ + + if (MDOC_BODY != node->type) + return(1); + term_word(p, "\\(lC"); + p->flags |= TERMP_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static void +termp_brq_post(DECL_ARGS) +{ + + if (MDOC_BODY != node->type) + return; + p->flags |= TERMP_NOSPACE; + term_word(p, "\\(rC"); +} + + +/* ARGSUSED */ +static int +termp_bq_pre(DECL_ARGS) +{ + + if (MDOC_BODY != node->type) + return(1); + term_word(p, "\\(lB"); + p->flags |= TERMP_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static void +termp_bq_post(DECL_ARGS) +{ + + if (MDOC_BODY != node->type) + return; + p->flags |= TERMP_NOSPACE; + term_word(p, "\\(rB"); +} + + +/* ARGSUSED */ +static int +termp_pq_pre(DECL_ARGS) +{ + + if (MDOC_BODY != node->type) + return(1); + term_word(p, "\\&("); + p->flags |= TERMP_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static void +termp_pq_post(DECL_ARGS) +{ + + if (MDOC_BODY != node->type) + return; + term_word(p, ")"); +} + + +/* ARGSUSED */ +static int +termp_fo_pre(DECL_ARGS) +{ + const struct mdoc_node *n; + + if (MDOC_BODY == node->type) { + term_word(p, "("); + p->flags |= TERMP_NOSPACE; + return(1); + } else if (MDOC_HEAD != node->type) + return(1); + + /* XXX - groff shows only first parameter */ + + p->flags |= ttypes[TTYPE_FUNC_NAME]; + for (n = node->child; n; n = n->next) { + if (MDOC_TEXT != n->type) + errx(1, "expected text line argument"); + term_word(p, n->string); + } + p->flags &= ~ttypes[TTYPE_FUNC_NAME]; + + return(0); +} + + +/* ARGSUSED */ +static void +termp_fo_post(DECL_ARGS) +{ + + if (MDOC_BODY != node->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 *n; + + if (MDOC_HEAD == node->type) { + return(0); + } else if (MDOC_BLOCK != node->type) + return(1); + + if (NULL == (n = node->head->child)) { + if (arg_hasattr(MDOC_Emphasis, node)) + TERMPAIR_SETFLAG(p, pair, ttypes[TTYPE_EMPH]); + else if (arg_hasattr(MDOC_Symbolic, node)) + TERMPAIR_SETFLAG(p, pair, ttypes[TTYPE_SYMB]); + + return(1); + } + + if (MDOC_TEXT != n->type) + errx(1, "expected text line arguments"); + + if (0 == strcmp("Em", n->string)) + TERMPAIR_SETFLAG(p, pair, ttypes[TTYPE_EMPH]); + else if (0 == strcmp("Sy", n->string)) + TERMPAIR_SETFLAG(p, pair, ttypes[TTYPE_EMPH]); + + return(1); +} + + +/* ARGSUSED */ +static int +termp_sy_pre(DECL_ARGS) +{ + + TERMPAIR_SETFLAG(p, pair, ttypes[TTYPE_SYMB]); + return(1); +} + + +/* ARGSUSED */ +static int +termp_ms_pre(DECL_ARGS) +{ + + TERMPAIR_SETFLAG(p, pair, ttypes[TTYPE_SYMBOL]); + return(1); +} + + + +/* ARGSUSED */ +static int +termp_sm_pre(DECL_ARGS) +{ + + if (NULL == node->child || MDOC_TEXT != node->child->type) + errx(1, "expected boolean line argument"); + + if (0 == strcmp("on", node->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 int +termp__j_pre(DECL_ARGS) +{ + + TERMPAIR_SETFLAG(p, pair, ttypes[TTYPE_REF_JOURNAL]); + return(1); +} + + +/* ARGSUSED */ +static int +termp__t_pre(DECL_ARGS) +{ + + term_word(p, "\""); + p->flags |= TERMP_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static void +termp__t_post(DECL_ARGS) +{ + + p->flags |= TERMP_NOSPACE; + term_word(p, "\""); + termp____post(p, pair, meta, node); +} + + +/* ARGSUSED */ +static void +termp____post(DECL_ARGS) +{ + + p->flags |= TERMP_NOSPACE; + term_word(p, node->next ? "," : "."); +} + + +/* ARGSUSED */ +static int +termp_lk_pre(DECL_ARGS) +{ + const struct mdoc_node *n; + + if (NULL == (n = node->child)) + errx(1, "expected line argument"); + + p->flags |= ttypes[TTYPE_LINK_ANCHOR]; + term_word(p, n->string); + p->flags &= ~ttypes[TTYPE_LINK_ANCHOR]; + p->flags |= TERMP_NOSPACE; + term_word(p, ":"); + + p->flags |= ttypes[TTYPE_LINK_TEXT]; + for ( ; n; n = n->next) { + term_word(p, n->string); + } + p->flags &= ~ttypes[TTYPE_LINK_TEXT]; + + return(0); +} + + +/* ARGSUSED */ +static int +termp_mt_pre(DECL_ARGS) +{ + + TERMPAIR_SETFLAG(p, pair, ttypes[TTYPE_LINK_ANCHOR]); + return(1); +} + + diff --git a/usr.bin/mandoc/mdoc_validate.c b/usr.bin/mandoc/mdoc_validate.c new file mode 100644 index 00000000000..b24c210edbf --- /dev/null +++ b/usr.bin/mandoc/mdoc_validate.c @@ -0,0 +1,1418 @@ +/* $Id: mdoc_validate.c,v 1.1 2009/04/06 20:30:40 kristaps Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@openbsd.org> + * + * 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 <sys/types.h> + +#include <assert.h> +#include <ctype.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> + +#include "libmdoc.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 + +enum merr { + ETOOLONG, + EESCAPE, + EPRINT, + ENODATA, + ENOPROLOGUE, + ELINE, + EATT, + ENAME, + ELISTTYPE, + EDISPTYPE, + EMULTIDISP, + EMULTILIST, + EARGREP, + EBOOL, + ENESTDISP +}; + +enum mwarn { + WPRINT, + WESCAPE, + WWRONGMSEC, + WSECOOO, + WSECREP, + WBADSTAND, + WNAMESECINC, + WNOMULTILINE, + WMULTILINE, + WLINE, + WNOLINE, + WPROLOOO, + WPROLREP, + WARGVAL, + WBADSEC, + WBADMSEC +}; + +typedef int (*v_pre)(PRE_ARGS); +typedef int (*v_post)(POST_ARGS); + +struct valids { + v_pre *pre; + v_post *post; +}; + +static int pwarn(struct mdoc *, int, int, enum mwarn); +static int perr(struct mdoc *, int, int, enum merr); +static int printwarn(struct mdoc *, int, int); +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 count_child(struct mdoc *); +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 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_prologue(PRE_ARGS); +static int pre_rv(PRE_ARGS); +static int pre_sh(PRE_ARGS); +static int pre_ss(PRE_ARGS); +static int herr_ge1(POST_ARGS); +static int hwarn_le1(POST_ARGS); +static int herr_eq0(POST_ARGS); +static int eerr_eq0(POST_ARGS); +static int eerr_le2(POST_ARGS); +static int eerr_eq1(POST_ARGS); +static int eerr_ge1(POST_ARGS); +static int ewarn_eq0(POST_ARGS); +static int ewarn_eq1(POST_ARGS); +static int bwarn_ge1(POST_ARGS); +static int hwarn_eq1(POST_ARGS); +static int ewarn_ge1(POST_ARGS); +static int ebool(POST_ARGS); +static int post_an(POST_ARGS); +static int post_args(POST_ARGS); +static int post_at(POST_ARGS); +static int post_bf(POST_ARGS); +static int post_bl(POST_ARGS); +static int post_it(POST_ARGS); +static int post_nm(POST_ARGS); +static int post_root(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); + +#define vwarn(m, t) nwarn((m), (m)->last, (t)) +#define verr(m, t) nerr((m), (m)->last, (t)) +#define nwarn(m, n, t) pwarn((m), (n)->line, (n)->pos, (t)) +#define nerr(m, n, t) perr((m), (n)->line, (n)->pos, (t)) + +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_dd[] = { pre_prologue, pre_dd, NULL }; +static v_pre pres_d1[] = { pre_display, NULL }; +static v_pre pres_dt[] = { pre_prologue, 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_prologue, 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 }; +static v_post posts_bool[] = { eerr_eq1, ebool, NULL }; +static v_post posts_bd[] = { herr_eq0, bwarn_ge1, NULL }; +static v_post posts_text[] = { eerr_ge1, NULL }; +static v_post posts_wtext[] = { ewarn_ge1, NULL }; +static v_post posts_notext[] = { eerr_eq0, NULL }; +static v_post posts_wline[] = { bwarn_ge1, herr_eq0, NULL }; +static v_post posts_sh[] = { herr_ge1, bwarn_ge1, post_sh, NULL }; +static v_post posts_bl[] = { herr_eq0, bwarn_ge1, post_bl, NULL }; +static v_post posts_it[] = { post_it, NULL }; +static v_post posts_in[] = { ewarn_eq1, NULL }; +static v_post posts_ss[] = { herr_ge1, NULL }; +static v_post posts_pf[] = { eerr_eq1, NULL }; +static v_post posts_lb[] = { eerr_eq1, NULL }; +static v_post posts_st[] = { eerr_eq1, post_st, NULL }; +static v_post posts_pp[] = { ewarn_eq0, NULL }; +static v_post posts_ex[] = { eerr_eq0, post_args, NULL }; +static v_post posts_rv[] = { eerr_eq0, post_args, NULL }; +static v_post posts_an[] = { post_an, NULL }; +static v_post posts_at[] = { post_at, NULL }; +static v_post posts_xr[] = { eerr_ge1, eerr_le2, NULL }; +static v_post posts_nm[] = { post_nm, NULL }; +static v_post posts_bf[] = { hwarn_le1, post_bf, NULL }; +static v_post posts_fo[] = { hwarn_eq1, bwarn_ge1, NULL }; + +const struct valids mdoc_valids[MDOC_MAX] = { + { NULL, NULL }, /* \" */ + { 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_pp }, /* 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, posts_ex }, /* 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_wtext }, /* Nd */ + { NULL, posts_nm }, /* Nm */ + { NULL, posts_wline }, /* Op */ + { NULL, NULL }, /* Ot */ + { NULL, NULL }, /* Pa */ + { pres_rv, posts_rv }, /* Rv */ + { NULL, posts_st }, /* St */ + { NULL, NULL }, /* Va */ + { NULL, posts_text }, /* Vt */ + { NULL, posts_xr }, /* Xr */ + { NULL, posts_text }, /* %A */ + { NULL, posts_text }, /* %B */ + { 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 */ + { 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_wline }, /* 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, NULL }, /* Ap */ + { NULL, posts_pp }, /* Lp */ + { NULL, posts_text }, /* 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 */ +}; + + +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; + + /* + * This check occurs after the macro's children have been filled + * in: postfix validation. Since this happens when we're + * rewinding the scope tree, it's possible to have multiple + * invocations (as by design, for now), we set bit MDOC_VALID to + * indicate that we've validated. + */ + + 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 +perr(struct mdoc *m, int line, int pos, enum merr type) +{ + char *p; + + p = NULL; + switch (type) { + case (ETOOLONG): + p = "text argument too long"; + break; + case (EESCAPE): + p = "invalid escape sequence"; + break; + case (EPRINT): + p = "invalid character"; + break; + case (ENESTDISP): + p = "displays may not be nested"; + break; + case (EBOOL): + p = "expected boolean value"; + break; + case (EARGREP): + p = "argument repeated"; + break; + case (EMULTIDISP): + p = "multiple display types specified"; + break; + case (EMULTILIST): + p = "multiple list types specified"; + break; + case (ELISTTYPE): + p = "missing list type"; + break; + case (EDISPTYPE): + p = "missing display type"; + break; + case (ELINE): + p = "expected line arguments"; + break; + case (ENOPROLOGUE): + p = "document has no prologue"; + break; + case (ENODATA): + p = "document has no data"; + break; + case (EATT): + p = "expected valid AT&T symbol"; + break; + case (ENAME): + p = "default name not yet set"; + break; + } + assert(p); + return(mdoc_perr(m, line, pos, p)); +} + + +static int +pwarn(struct mdoc *m, int line, int pos, enum mwarn type) +{ + char *p; + enum mdoc_warn c; + + c = WARN_SYNTAX; + p = NULL; + switch (type) { + case (WPRINT): + p = "invalid character"; + break; + case (WBADMSEC): + p = "inappropriate manual section"; + c = WARN_COMPAT; + break; + case (WBADSEC): + p = "inappropriate document section"; + c = WARN_COMPAT; + break; + case (WARGVAL): + p = "argument value suggested"; + c = WARN_COMPAT; + break; + case (WPROLREP): + p = "prologue macros repeated"; + c = WARN_COMPAT; + break; + case (WPROLOOO): + p = "prologue macros out-of-order"; + c = WARN_COMPAT; + break; + case (WESCAPE): + p = "invalid escape sequence"; + break; + case (WNOLINE): + p = "suggested no line arguments"; + break; + case (WLINE): + p = "suggested line arguments"; + break; + case (WMULTILINE): + p = "suggested multi-line arguments"; + break; + case (WNOMULTILINE): + p = "suggested no multi-line arguments"; + break; + case (WWRONGMSEC): + p = "document section in wrong manual section"; + c = WARN_COMPAT; + break; + case (WSECOOO): + p = "document section out of conventional order"; + break; + case (WSECREP): + p = "document section repeated"; + break; + case (WBADSTAND): + p = "unknown standard"; + break; + case (WNAMESECINC): + p = "NAME section contents incomplete/badly-ordered"; + break; + } + assert(p); + return(mdoc_pwarn(m, line, pos, c, p)); +} + + + +static inline int +warn_count(struct mdoc *m, const char *k, + int want, const char *v, int has) +{ + + return(mdoc_warn(m, WARN_SYNTAX, + "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_err(m, + "requires %s %s %d (has %d)", v, k, want, has)); +} + + +static inline int +count_child(struct mdoc *mdoc) +{ + int i; + struct mdoc_node *n; + + for (i = 0, n = mdoc->last->child; n; n = n->next, i++) + /* Do nothing */ ; + + return(i); +} + + +/* + * 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) \ +{ \ + int i; \ + if ((i = count_child(mdoc)) ineq sz) \ + return(1); \ + return(lvl##_count(mdoc, #ineq, sz, p, i)); \ +} + +#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_ELEM_DEFN(eq1, warn, warn_child_eq, 1) /* ewarn_eq1() */ +CHECK_ELEM_DEFN(eq0, warn, warn_child_eq, 0) /* ewarn_eq0() */ +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(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(nwarn(mdoc, n, WARGVAL)); +} + + +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(nwarn(mdoc, n, WBADSEC)); +} + + +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(nwarn(mdoc, n, WBADMSEC)); +} + + +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(nerr(m, n, ENAME)); + } + + return(1); +} + + +static int +printwarn(struct mdoc *m, int ln, int pos) +{ + if (MDOC_IGN_CHARS & m->pflags) + return(pwarn(m, ln, pos, WPRINT)); + return(perr(m, ln, pos, EPRINT)); +} + + +static int +check_text(struct mdoc *mdoc, int line, int pos, const char *p) +{ + size_t c; + + /* FIXME: indicate deprecated escapes \*(xx and \*x. */ + + for ( ; *p; p++) { + if ('\t' == *p) { + if ( ! (MDOC_LITERAL & mdoc->flags)) + if ( ! printwarn(mdoc, line, pos)) + return(0); + } else if ( ! isprint((u_char)*p)) + if ( ! printwarn(mdoc, line, pos)) + return(0); + + if ('\\' != *p) + continue; + + c = mdoc_isescape(p); + if (c) { + p += (int)c - 1; + continue; + } + if ( ! (MDOC_IGN_ESCAPE & mdoc->pflags)) + return(perr(mdoc, line, pos, EESCAPE)); + if ( ! pwarn(mdoc, line, pos, WESCAPE)) + 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_nerr(mdoc, n, "require parent %s", + MDOC_ROOT == t ? "<root>" : 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(nerr(mdoc, n, ENESTDISP)); +} + + +static int +pre_bl(PRE_ARGS) +{ + int i, type, width, offset; + + if (MDOC_BLOCK != n->type) + return(1); + if (NULL == n->args) + return(nerr(mdoc, n, ELISTTYPE)); + + /* Make sure that only one type of list is specified. */ + + type = offset = width = -1; + + /* LINTED */ + for (i = 0; i < (int)n->args->argc; 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_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 (-1 == type) { + type = n->args->argv[i].arg; + break; + } + return(nerr(mdoc, n, EMULTILIST)); + case (MDOC_Width): + if (-1 == width) { + width = n->args->argv[i].arg; + break; + } + return(nerr(mdoc, n, EARGREP)); + case (MDOC_Offset): + if (-1 == offset) { + offset = n->args->argv[i].arg; + break; + } + return(nerr(mdoc, n, EARGREP)); + default: + break; + } + + if (-1 == type) + return(nerr(mdoc, n, ELISTTYPE)); + + switch (type) { + case (MDOC_Column): + /* FALLTHROUGH */ + case (MDOC_Diag): + /* FALLTHROUGH */ + case (MDOC_Inset): + /* FALLTHROUGH */ + case (MDOC_Item): + if (-1 == width) + break; + return(mdoc_nwarn(mdoc, n, WARN_SYNTAX, + "superfluous %s argument", + mdoc_argnames[MDOC_Width])); + case (MDOC_Tag): + if (-1 != width) + break; + return(mdoc_nwarn(mdoc, n, WARN_SYNTAX, + "suggest %s argument", + mdoc_argnames[MDOC_Width])); + 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(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): + /* FALLTHROUGH */ + case (MDOC_File): + if (0 == type++) + break; + return(nerr(mdoc, n, EMULTIDISP)); + default: + break; + } + + if (type) + return(1); + return(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_nerr(mdoc, n, "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, 0)); +} + + +static int +pre_cd(PRE_ARGS) +{ + + return(check_msec(mdoc, n, 4, 0)); +} + + +static int +pre_prologue(PRE_ARGS) +{ + + return(check_sec(mdoc, n, SEC_PROLOGUE, SEC_CUSTOM)); +} + + +static int +pre_dt(PRE_ARGS) +{ + + if (0 == mdoc->meta.date || mdoc->meta.os) + if ( ! nwarn(mdoc, n, WPROLOOO)) + return(0); + if (mdoc->meta.title) + if ( ! nwarn(mdoc, n, WPROLREP)) + return(0); + return(1); +} + + +static int +pre_os(PRE_ARGS) +{ + + if (NULL == mdoc->meta.title || 0 == mdoc->meta.date) + if ( ! nwarn(mdoc, n, WPROLOOO)) + return(0); + if (mdoc->meta.os) + if ( ! nwarn(mdoc, n, WPROLREP)) + return(0); + return(1); +} + + +static int +pre_dd(PRE_ARGS) +{ + + if (mdoc->meta.title || mdoc->meta.os) + if ( ! nwarn(mdoc, n, WPROLOOO)) + return(0); + if (mdoc->meta.date) + if ( ! nwarn(mdoc, n, WPROLREP)) + 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 (NULL == mdoc->last->args) { + if (NULL == head->child || + MDOC_TEXT != head->child->type) + return(mdoc_err(mdoc, "text argument expected")); + + p = head->child->string; + if (0 == strcmp(p, "Em")) + return(1); + else if (0 == strcmp(p, "Li")) + return(1); + else if (0 == strcmp(p, "Sm")) + return(1); + return(mdoc_nerr(mdoc, head->child, "invalid font")); + } + + if (head->child) + return(mdoc_err(mdoc, "one argument expected")); + + return(1); +} + + +static int +post_nm(POST_ARGS) +{ + + if (mdoc->last->child) + return(1); + if (mdoc->meta.name) + return(1); + return(verr(mdoc, ENAME)); +} + + +static int +post_at(POST_ARGS) +{ + + if (NULL == mdoc->last->child) + return(1); + if (MDOC_TEXT != mdoc->last->child->type) + return(verr(mdoc, EATT)); + if (mdoc_a2att(mdoc->last->child->string)) + return(1); + return(verr(mdoc, EATT)); +} + + +static int +post_an(POST_ARGS) +{ + + if (mdoc->last->args) { + if (NULL == mdoc->last->child) + return(1); + return(verr(mdoc, ELINE)); + } + + if (mdoc->last->child) + return(1); + return(verr(mdoc, ELINE)); +} + + +static int +post_args(POST_ARGS) +{ + + if (mdoc->last->args) + return(1); + return(verr(mdoc, 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(verr(mdoc, 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(verr(mdoc, ELISTTYPE)); + + switch (type) { + case (MDOC_Tag): + if (NULL == mdoc->last->head->child) + if ( ! vwarn(mdoc, WLINE)) + 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 ( ! vwarn(mdoc, WLINE)) + return(0); + if (NULL == mdoc->last->body->child) + if ( ! vwarn(mdoc, WMULTILINE)) + 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 ( ! vwarn(mdoc, WNOLINE)) + return(0); + if (NULL == mdoc->last->body->child) + if ( ! vwarn(mdoc, WMULTILINE)) + return(0); + break; + case (MDOC_Column): + if (NULL == mdoc->last->head->child) + if ( ! vwarn(mdoc, WLINE)) + return(0); + if (mdoc->last->body->child) + if ( ! vwarn(mdoc, WNOMULTILINE)) + return(0); + c = mdoc->last->child; + for (i = 0; c && MDOC_HEAD == c->type; c = c->next) + i++; + if (i == cols) + break; + return(mdoc_err(mdoc, "column mismatch (have " + "%d, want %d)", i, cols)); + default: + break; + } + + return(1); +} + + +static int +post_bl(POST_ARGS) +{ + struct mdoc_node *n; + + 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, "bad child of parent %s", + mdoc_macronames[mdoc->last->tok])); + } + + 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(nerr(mdoc, n, EBOOL)); +} + + +static int +post_root(POST_ARGS) +{ + + if (NULL == mdoc->first->child) + return(verr(mdoc, ENODATA)); + if (SEC_PROLOGUE == mdoc->lastnamed) + return(verr(mdoc, ENOPROLOGUE)); + + if (MDOC_BLOCK != mdoc->first->child->type) + return(verr(mdoc, ENODATA)); + if (MDOC_Sh != mdoc->first->child->tok) + return(verr(mdoc, ENODATA)); + + return(1); +} + + +static int +post_st(POST_ARGS) +{ + + if (mdoc_a2st(mdoc->last->child->string)) + return(1); + return(vwarn(mdoc, WBADSTAND)); +} + + +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->lastnamed) + 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(vwarn(mdoc, WNAMESECINC)); + + for ( ; n && n->next; n = n->next) { + if (MDOC_ELEM == n->type && MDOC_Nm == n->tok) + continue; + if (MDOC_TEXT == n->type) + continue; + if ( ! vwarn(mdoc, WNAMESECINC)) + return(0); + } + + if (MDOC_ELEM == n->type && MDOC_Nd == n->tok) + return(1); + return(vwarn(mdoc, WNAMESECINC)); +} + + +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. + */ + + assert(MDOC_Sh == mdoc->last->tok); + + /* This is just concat() inlined, which is irritating. */ + + buf[0] = 0; + for (n = mdoc->last->child; n; n = n->next) { + assert(MDOC_TEXT == n->type); + if (strlcat(buf, n->string, 64) >= 64) + return(nerr(mdoc, n, ETOOLONG)); + if (NULL == n->next) + continue; + if (strlcat(buf, " ", 64) >= 64) + return(nerr(mdoc, n, ETOOLONG)); + } + + sec = mdoc_atosec(buf); + + /* The NAME section should always be first. */ + + if (SEC_BODY == mdoc->lastnamed && SEC_NAME != sec) + return(vwarn(mdoc, WSECOOO)); + if (SEC_CUSTOM == sec) + return(1); + + /* Check for repeated or out-of-order sections. */ + + if (sec == mdoc->lastnamed) + return(vwarn(mdoc, WSECREP)); + if (sec < mdoc->lastnamed) + return(vwarn(mdoc, WSECOOO)); + + /* Check particular section/manual section conventions. */ + + switch (sec) { + case (SEC_LIBRARY): + switch (mdoc->meta.msec) { + case (2): + /* FALLTHROUGH */ + case (3): + break; + default: + return(vwarn(mdoc, WWRONGMSEC)); + } + 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 00000000000..acd4090e16b --- /dev/null +++ b/usr.bin/mandoc/msec.c @@ -0,0 +1,34 @@ +/* $Id: msec.c,v 1.1 2009/04/06 20:30:40 kristaps Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons <kristaps@openbsd.org> + * + * 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 <stdlib.h> +#include <string.h> + +#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 00000000000..5b5ef46dce3 --- /dev/null +++ b/usr.bin/mandoc/msec.in @@ -0,0 +1,42 @@ +/* $Id: msec.in,v 1.1 2009/04/06 20:30:40 kristaps Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons <kristaps@openbsd.org> + * + * 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", "General Commands Manual") +LINE("2", "System Calls Manual") +LINE("3", "Library Functions Manual") +LINE("3p", "Perl Library Functions Manual") +LINE("4", "Kernel Interfaces Manual") +LINE("5", "File Formats Manual") +LINE("6", "Games Manual") +LINE("7", "Miscellaneous Information Manual") +LINE("8", "System Manager\'s Manual") +LINE("9", "Kernel Developer\'s 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/st.c b/usr.bin/mandoc/st.c new file mode 100644 index 00000000000..8459c902e77 --- /dev/null +++ b/usr.bin/mandoc/st.c @@ -0,0 +1,34 @@ +/* $Id: st.c,v 1.1 2009/04/06 20:30:40 kristaps Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons <kristaps@openbsd.org> + * + * 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 <stdlib.h> +#include <string.h> + +#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 00000000000..c605cf1c889 --- /dev/null +++ b/usr.bin/mandoc/st.in @@ -0,0 +1,49 @@ +/* $Id: st.in,v 1.1 2009/04/06 20:30:40 kristaps Exp $ */ + +/* + * 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.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-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("-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.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 00000000000..f9ad1ec8963 --- /dev/null +++ b/usr.bin/mandoc/term.c @@ -0,0 +1,601 @@ +/* $Id: term.c,v 1.1 2009/04/06 20:30:40 kristaps Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@openbsd.org> + * + * 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 <assert.h> +#include <err.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "term.h" +#include "man.h" +#include "mdoc.h" + +extern int man_run(struct termp *, + const struct man *); +extern int mdoc_run(struct termp *, + const struct mdoc *); + +static struct termp *term_alloc(enum termenc); +static void term_free(struct termp *); +static void term_pword(struct termp *, const char *, int); +static void term_pescape(struct termp *, + const char *, int *, int); +static void term_nescape(struct termp *, + const char *, size_t); +static void term_chara(struct termp *, char); +static void term_stringa(struct termp *, + const char *, size_t); +static int term_isopendelim(const char *, int); +static int term_isclosedelim(const char *, int); + + +void * +ascii_alloc(void) +{ + + return(term_alloc(TERMENC_ASCII)); +} + + +int +terminal_man(void *arg, const struct man *man) +{ + struct termp *p; + + p = (struct termp *)arg; + if (NULL == p->symtab) + p->symtab = term_ascii2htab(); + + return(man_run(p, man)); +} + + +int +terminal_mdoc(void *arg, const struct mdoc *mdoc) +{ + struct termp *p; + + p = (struct termp *)arg; + if (NULL == p->symtab) + p->symtab = term_ascii2htab(); + + return(mdoc_run(p, mdoc)); +} + + +void +terminal_free(void *arg) +{ + + term_free((struct termp *)arg); +} + + +static void +term_free(struct termp *p) +{ + + if (p->buf) + free(p->buf); + if (TERMENC_ASCII == p->enc && p->symtab) + term_asciifree(p->symtab); + + free(p); +} + + +static struct termp * +term_alloc(enum termenc enc) +{ + struct termp *p; + + if (NULL == (p = malloc(sizeof(struct termp)))) + err(1, "malloc"); + bzero(p, sizeof(struct termp)); + p->maxrmargin = 78; + p->enc = enc; + return(p); +} + + +static int +term_isclosedelim(const char *p, int len) +{ + + if (1 != len) + return(0); + + switch (*p) { + case('.'): + /* FALLTHROUGH */ + case(','): + /* FALLTHROUGH */ + case(';'): + /* FALLTHROUGH */ + case(':'): + /* FALLTHROUGH */ + case('?'): + /* FALLTHROUGH */ + case('!'): + /* FALLTHROUGH */ + case(')'): + /* FALLTHROUGH */ + case(']'): + /* FALLTHROUGH */ + case('}'): + return(1); + default: + break; + } + + return(0); +} + + +static int +term_isopendelim(const char *p, int len) +{ + + if (1 != len) + return(0); + + switch (*p) { + case('('): + /* FALLTHROUGH */ + case('['): + /* FALLTHROUGH */ + case('{'): + return(1); + default: + break; + } + + return(0); +} + + +/* + * 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 variables TERMP_NOLPAD, TERMP_LITERAL and TERMP_NOBREAK are of + * critical importance here. Their behaviour 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_NONOBREAK: don't newline when TERMP_NOBREAK is specified. + * + * 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 vsz, vis, maxvis, mmax, bp; + + /* + * 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); + maxvis = p->rmargin - p->offset; + mmax = p->maxrmargin - p->offset; + 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 (' ' == p->buf[j]) + break; + else if (8 == p->buf[j]) + j += 1; + else + vsz++; + } + + /* + * Do line-breaking. If we're greater than our + * break-point and already in-line, break to the next + * line and start writing. If we're at the line start, + * then write out the word (TODO: hyphenate) and break + * in a subsequent loop invocation. + */ + + if ( ! (TERMP_NOBREAK & p->flags)) { + if (vis && vis + vsz > bp) { + putchar('\n'); + for (j = 0; j < (int)p->offset; j++) + putchar(' '); + vis = 0; + } + } else if (vis && vis + vsz > bp) { + putchar('\n'); + for (j = 0; j < (int)p->rmargin; j++) + putchar(' '); + vis = p->rmargin - p->offset; + } + + /* + * Write out the word and a trailing space. Omit the + * space if we're the last word in the line or beyond + * our breakpoint. + */ + + for ( ; i < (int)p->col; i++) { + if (' ' == p->buf[i]) + break; + putchar(p->buf[i]); + } + vis += vsz; + if (i < (int)p->col && vis <= bp) { + putchar(' '); + vis++; + } + } + + /* + * If we've overstepped our maximum visible no-break space, then + * cause a newline and offset at the right margin. + */ + + if ((TERMP_NOBREAK & p->flags) && vis >= maxvis) { + if ( ! (TERMP_NONOBREAK & p->flags)) { + putchar('\n'); + for (i = 0; i < (int)p->rmargin; i++) + putchar(' '); + } + p->col = 0; + return; + } + + /* + * If we're not to right-marginalise it (newline), then instead + * pad to the right margin and stay off. + */ + + if (p->flags & TERMP_NOBREAK) { + if ( ! (TERMP_NONOBREAK & p->flags)) + for ( ; vis < maxvis; vis++) + putchar(' '); + } else + putchar('\n'); + + p->col = 0; +} + + +/* + * 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'); +} + + +/* + * Break apart a word into "pwords" (partial-words, usually from + * breaking up a phrase into individual words) and, eventually, put them + * into the output buffer. If we're a literal word, then don't break up + * the word and put it verbatim into the output buffer. + */ +void +term_word(struct termp *p, const char *word) +{ + int i, j, len; + + len = (int)strlen(word); + + if (p->flags & TERMP_LITERAL) { + term_pword(p, word, len); + return; + } + + /* LINTED */ + for (j = i = 0; i < len; i++) { + if (' ' != word[i]) { + j++; + continue; + } + + /* Escaped spaces don't delimit... */ + if (i && ' ' == word[i] && '\\' == word[i - 1]) { + j++; + continue; + } + + if (0 == j) + continue; + assert(i >= j); + term_pword(p, &word[i - j], j); + j = 0; + } + if (j > 0) { + assert(i >= j); + term_pword(p, &word[i - j], j); + } +} + + +/* + * Determine the symbol indicated by an escape sequences, that is, one + * starting with a backslash. Once done, we pass this value into the + * output buffer by way of the symbol table. + */ +static void +term_nescape(struct termp *p, const char *word, size_t len) +{ + const char *rhs; + size_t sz; + + if (NULL == (rhs = term_a2ascii(p->symtab, word, len, &sz))) + return; + term_stringa(p, rhs, sz); +} + + +/* + * 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 +term_pescape(struct termp *p, const char *word, int *i, int len) +{ + int j; + + if (++(*i) >= len) + return; + + if ('(' == word[*i]) { + (*i)++; + if (*i + 1 >= len) + return; + + term_nescape(p, &word[*i], 2); + (*i)++; + return; + + } else if ('*' == word[*i]) { + (*i)++; + if (*i >= len) + return; + + switch (word[*i]) { + case ('('): + (*i)++; + if (*i + 1 >= len) + return; + + term_nescape(p, &word[*i], 2); + (*i)++; + return; + case ('['): + break; + default: + term_nescape(p, &word[*i], 1); + return; + } + + } else if ('f' == word[*i]) { + (*i)++; + if (*i >= len) + return; + switch (word[*i]) { + case ('B'): + p->flags |= TERMP_BOLD; + break; + case ('I'): + p->flags |= TERMP_UNDER; + break; + case ('P'): + /* FALLTHROUGH */ + case ('R'): + p->flags &= ~TERMP_STYLE; + break; + default: + break; + } + return; + + } else if ('[' != word[*i]) { + term_nescape(p, &word[*i], 1); + return; + } + + (*i)++; + for (j = 0; word[*i] && ']' != word[*i]; (*i)++, j++) + /* Loop... */ ; + + if (0 == word[*i]) + return; + + term_nescape(p, &word[*i - j], (size_t)j); +} + + +/* + * 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. + */ +static void +term_pword(struct termp *p, const char *word, int len) +{ + int i; + + if (term_isclosedelim(word, len)) + if ( ! (TERMP_IGNDELIM & p->flags)) + p->flags |= TERMP_NOSPACE; + + if ( ! (TERMP_NOSPACE & p->flags)) + term_chara(p, ' '); + + if ( ! (p->flags & TERMP_NONOSPACE)) + p->flags &= ~TERMP_NOSPACE; + + /* + * If ANSI (word-length styling), then apply our style now, + * before the word. + */ + + for (i = 0; i < len; i++) { + if ('\\' == word[i]) { + term_pescape(p, word, &i, len); + continue; + } + + if (TERMP_STYLE & p->flags) { + if (TERMP_BOLD & p->flags) { + term_chara(p, word[i]); + term_chara(p, 8); + } + if (TERMP_UNDER & p->flags) { + term_chara(p, '_'); + term_chara(p, 8); + } + } + + term_chara(p, word[i]); + } + + if (term_isopendelim(word, len)) + p->flags |= TERMP_NOSPACE; +} + + +/* + * Like term_chara() but for arbitrary-length buffers. Resize the + * buffer by a factor of two (if the buffer is less than that) or the + * buffer's size. + */ +static void +term_stringa(struct termp *p, const char *c, size_t sz) +{ + size_t s; + + if (0 == sz) + return; + + assert(c); + if (p->col + sz >= p->maxcols) { + if (0 == p->maxcols) + p->maxcols = 256; + s = sz > p->maxcols * 2 ? sz : p->maxcols * 2; + p->buf = realloc(p->buf, s); + if (NULL == p->buf) + err(1, "realloc"); + p->maxcols = s; + } + + (void)memcpy(&p->buf[(int)p->col], c, sz); + p->col += sz; +} + + +/* + * 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 +term_chara(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"); + p->maxcols = s; + } + p->buf[(int)(p->col)++] = c; +} + diff --git a/usr.bin/mandoc/term.h b/usr.bin/mandoc/term.h new file mode 100644 index 00000000000..95c684de43b --- /dev/null +++ b/usr.bin/mandoc/term.h @@ -0,0 +1,67 @@ +/* $Id: term.h,v 1.1 2009/04/06 20:30:40 kristaps Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@openbsd.org> + * + * 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 + +/* FIXME - clean up tabs. */ + +#define INDENT 6 + +__BEGIN_DECLS + +enum termenc { + TERMENC_ASCII, + TERMENC_LATIN1, /* Not implemented. */ + TERMENC_UTF8 /* Not implemented. */ +}; + +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 << 0) /* No space before words. */ +#define TERMP_NOLPAD (1 << 1) /* No leftpad before flush. */ +#define TERMP_NOBREAK (1 << 2) /* No break after flush. */ +#define TERMP_LITERAL (1 << 3) /* Literal words. */ +#define TERMP_IGNDELIM (1 << 4) /* Delims like regulars. */ +#define TERMP_NONOSPACE (1 << 5) /* No space (no autounset). */ +#define TERMP_NONOBREAK (1 << 7) /* Don't newln NOBREAK. */ +#define TERMP_STYLE 0x0300 /* Style mask. */ +#define TERMP_BOLD (1 << 8) /* Styles... */ +#define TERMP_UNDER (1 << 9) + char *buf; /* Output buffer. */ + enum termenc enc; /* Type of encoding. */ + void *symtab; /* Encoded-symbol table. */ +}; + +void *term_ascii2htab(void); +const char *term_a2ascii(void *, const char *, size_t, size_t *); +void term_asciifree(void *); + +void term_newln(struct termp *); +void term_vspace(struct termp *); +void term_word(struct termp *, const char *); +void term_flushln(struct termp *); + +__END_DECLS + +#endif /*!TERM_H*/ diff --git a/usr.bin/mandoc/tree.c b/usr.bin/mandoc/tree.c new file mode 100644 index 00000000000..df131d06d4f --- /dev/null +++ b/usr.bin/mandoc/tree.c @@ -0,0 +1,196 @@ +/* $Id: tree.c,v 1.1 2009/04/06 20:30:40 kristaps Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@openbsd.org> + * + * 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 <assert.h> +#include <err.h> +#include <stdio.h> +#include <stdlib.h> + +#include "mdoc.h" +#include "man.h" + +static void print_mdoc(const struct mdoc_node *, int); +static void print_man(const struct man_node *, int); + + +/* ARGSUSED */ +int +tree_mdoc(void *arg, const struct mdoc *mdoc) +{ + + print_mdoc(mdoc_node(mdoc), 0); + return(1); +} + + +/* ARGSUSED */ +int +tree_man(void *arg, const struct man *man) +{ + + print_man(man_node(man), 0); + return(1); +} + + +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; + default: + abort(); + /* NOTREACHED */ + } + + switch (n->type) { + case (MAN_TEXT): + p = n->string; + break; + case (MAN_ELEM): + 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 00000000000..c40ff73bc7b --- /dev/null +++ b/usr.bin/mandoc/vol.c @@ -0,0 +1,34 @@ +/* $Id: vol.c,v 1.1 2009/04/06 20:30:40 kristaps Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons <kristaps@openbsd.org> + * + * 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 <stdlib.h> +#include <string.h> + +#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 00000000000..2c8bdda8a17 --- /dev/null +++ b/usr.bin/mandoc/vol.in @@ -0,0 +1,37 @@ +/* $Id: vol.in,v 1.1 2009/04/06 20:30:40 kristaps Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons <kristaps@openbsd.org> + * + * 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") |