summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorkristaps <kristaps@openbsd.org>2009-04-06 20:30:40 +0000
committerkristaps <kristaps@openbsd.org>2009-04-06 20:30:40 +0000
commitf73abda98a4c5213d9349b368e2be8a3b49b5b74 (patch)
treedf64e8a874e335669bc9a8bfd2c0c95183ccca2c
parentsync with 0.9.8k; (diff)
downloadwireguard-openbsd-f73abda98a4c5213d9349b368e2be8a3b49b5b74.tar.xz
wireguard-openbsd-f73abda98a4c5213d9349b368e2be8a3b49b5b74.zip
Initial check-in of mandoc for formatting manuals. ok deraadt@
-rw-r--r--usr.bin/mandoc/Makefile13
-rw-r--r--usr.bin/mandoc/arch.c34
-rw-r--r--usr.bin/mandoc/arch.in54
-rw-r--r--usr.bin/mandoc/ascii.c191
-rw-r--r--usr.bin/mandoc/ascii.in219
-rw-r--r--usr.bin/mandoc/att.c34
-rw-r--r--usr.bin/mandoc/att.in39
-rw-r--r--usr.bin/mandoc/lib.c34
-rw-r--r--usr.bin/mandoc/lib.in59
-rw-r--r--usr.bin/mandoc/libman.h62
-rw-r--r--usr.bin/mandoc/libmdoc.h157
-rw-r--r--usr.bin/mandoc/main.c671
-rw-r--r--usr.bin/mandoc/man.3279
-rw-r--r--usr.bin/mandoc/man.7205
-rw-r--r--usr.bin/mandoc/man.c440
-rw-r--r--usr.bin/mandoc/man.h104
-rw-r--r--usr.bin/mandoc/man_action.c192
-rw-r--r--usr.bin/mandoc/man_hash.c93
-rw-r--r--usr.bin/mandoc/man_macro.c228
-rw-r--r--usr.bin/mandoc/man_term.c523
-rw-r--r--usr.bin/mandoc/man_validate.c137
-rw-r--r--usr.bin/mandoc/mandoc.1289
-rw-r--r--usr.bin/mandoc/mandoc_char.7469
-rw-r--r--usr.bin/mandoc/manuals.7257
-rw-r--r--usr.bin/mandoc/mdoc.3346
-rw-r--r--usr.bin/mandoc/mdoc.7604
-rw-r--r--usr.bin/mandoc/mdoc.c651
-rw-r--r--usr.bin/mandoc/mdoc.h328
-rw-r--r--usr.bin/mandoc/mdoc_action.c783
-rw-r--r--usr.bin/mandoc/mdoc_argv.c872
-rw-r--r--usr.bin/mandoc/mdoc_hash.c174
-rw-r--r--usr.bin/mandoc/mdoc_macro.c1496
-rw-r--r--usr.bin/mandoc/mdoc_strings.c316
-rw-r--r--usr.bin/mandoc/mdoc_term.c2238
-rw-r--r--usr.bin/mandoc/mdoc_validate.c1418
-rw-r--r--usr.bin/mandoc/msec.c34
-rw-r--r--usr.bin/mandoc/msec.in42
-rw-r--r--usr.bin/mandoc/st.c34
-rw-r--r--usr.bin/mandoc/st.in49
-rw-r--r--usr.bin/mandoc/term.c601
-rw-r--r--usr.bin/mandoc/term.h67
-rw-r--r--usr.bin/mandoc/tree.c196
-rw-r--r--usr.bin/mandoc/vol.c34
-rw-r--r--usr.bin/mandoc/vol.in37
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")