Bring in OpenBSD's mandoc(1) tool for formatting manual pages.
authorSascha Wildner <saw@online.de>
Tue, 27 Oct 2009 08:42:29 +0000 (09:42 +0100)
committerSascha Wildner <saw@online.de>
Tue, 27 Oct 2009 08:42:29 +0000 (09:42 +0100)
It's not (yet) used by anything but it is quite useful for
error checking.

Thanks to Kristaps Dzonsons and OpenBSD!

56 files changed:
usr.bin/Makefile
usr.bin/mandoc/Makefile [new file with mode: 0644]
usr.bin/mandoc/arch.c [new file with mode: 0644]
usr.bin/mandoc/arch.in [new file with mode: 0644]
usr.bin/mandoc/att.c [new file with mode: 0644]
usr.bin/mandoc/att.in [new file with mode: 0644]
usr.bin/mandoc/chars.c [new file with mode: 0644]
usr.bin/mandoc/chars.h [new file with mode: 0644]
usr.bin/mandoc/chars.in [new file with mode: 0644]
usr.bin/mandoc/html.c [new file with mode: 0644]
usr.bin/mandoc/html.h [new file with mode: 0644]
usr.bin/mandoc/lib.c [new file with mode: 0644]
usr.bin/mandoc/lib.in [new file with mode: 0644]
usr.bin/mandoc/libman.h [new file with mode: 0644]
usr.bin/mandoc/libmandoc.h [new file with mode: 0644]
usr.bin/mandoc/libmdoc.h [new file with mode: 0644]
usr.bin/mandoc/main.c [new file with mode: 0644]
usr.bin/mandoc/main.h [new file with mode: 0644]
usr.bin/mandoc/man.3 [new file with mode: 0644]
usr.bin/mandoc/man.7 [new file with mode: 0644]
usr.bin/mandoc/man.c [new file with mode: 0644]
usr.bin/mandoc/man.h [new file with mode: 0644]
usr.bin/mandoc/man_action.c [new file with mode: 0644]
usr.bin/mandoc/man_argv.c [new file with mode: 0644]
usr.bin/mandoc/man_hash.c [new file with mode: 0644]
usr.bin/mandoc/man_html.c [new file with mode: 0644]
usr.bin/mandoc/man_macro.c [new file with mode: 0644]
usr.bin/mandoc/man_term.c [new file with mode: 0644]
usr.bin/mandoc/man_validate.c [new file with mode: 0644]
usr.bin/mandoc/mandoc.1 [new file with mode: 0644]
usr.bin/mandoc/mandoc.c [new file with mode: 0644]
usr.bin/mandoc/mandoc_char.7 [new file with mode: 0644]
usr.bin/mandoc/manuals.7 [new file with mode: 0644]
usr.bin/mandoc/mdoc.3 [new file with mode: 0644]
usr.bin/mandoc/mdoc.7 [new file with mode: 0644]
usr.bin/mandoc/mdoc.c [new file with mode: 0644]
usr.bin/mandoc/mdoc.h [new file with mode: 0644]
usr.bin/mandoc/mdoc_action.c [new file with mode: 0644]
usr.bin/mandoc/mdoc_argv.c [new file with mode: 0644]
usr.bin/mandoc/mdoc_hash.c [new file with mode: 0644]
usr.bin/mandoc/mdoc_html.c [new file with mode: 0644]
usr.bin/mandoc/mdoc_macro.c [new file with mode: 0644]
usr.bin/mandoc/mdoc_strings.c [new file with mode: 0644]
usr.bin/mandoc/mdoc_term.c [new file with mode: 0644]
usr.bin/mandoc/mdoc_validate.c [new file with mode: 0644]
usr.bin/mandoc/msec.c [new file with mode: 0644]
usr.bin/mandoc/msec.in [new file with mode: 0644]
usr.bin/mandoc/out.c [new file with mode: 0644]
usr.bin/mandoc/out.h [new file with mode: 0644]
usr.bin/mandoc/st.c [new file with mode: 0644]
usr.bin/mandoc/st.in [new file with mode: 0644]
usr.bin/mandoc/term.c [new file with mode: 0644]
usr.bin/mandoc/term.h [new file with mode: 0644]
usr.bin/mandoc/tree.c [new file with mode: 0644]
usr.bin/mandoc/vol.c [new file with mode: 0644]
usr.bin/mandoc/vol.in [new file with mode: 0644]

index c19ca50..04a3630 100644 (file)
@@ -109,6 +109,7 @@ SUBDIR=     alias \
        m4 \
        mail \
        make \
+       mandoc \
        mesg \
        mkdep \
        mkfifo \
diff --git a/usr.bin/mandoc/Makefile b/usr.bin/mandoc/Makefile
new file mode 100644 (file)
index 0000000..46882b3
--- /dev/null
@@ -0,0 +1,17 @@
+#      $OpenBSD: Makefile,v 1.19 2009/10/19 09:16:58 schwarze Exp $
+
+VERSION=1.9.9
+CFLAGS+=-DVERSION=\"${VERSION}\"
+WARNS?=        3
+
+SRCS=  mandoc.c mdoc_macro.c mdoc.c mdoc_hash.c mdoc_strings.c \
+       mdoc_argv.c mdoc_validate.c mdoc_action.c lib.c att.c \
+       arch.c vol.c msec.c st.c
+SRCS+= man_macro.c man.c man_hash.c man_validate.c \
+       man_action.c man_argv.c
+SRCS+= main.c mdoc_term.c chars.c term.c tree.c man_term.c
+SRCS+= html.c mdoc_html.c man_html.c out.c
+
+PROG=  mandoc
+
+.include <bsd.prog.mk>
diff --git a/usr.bin/mandoc/arch.c b/usr.bin/mandoc/arch.c
new file mode 100644 (file)
index 0000000..0459e1f
--- /dev/null
@@ -0,0 +1,32 @@
+/*     $Id: arch.c,v 1.2 2009/06/14 23:00:57 schwarze Exp $ */
+/*
+ * Copyright (c) 2009 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 <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 (file)
index 0000000..b1c23ee
--- /dev/null
@@ -0,0 +1,52 @@
+/*     $Id: arch.in,v 1.2 2009/06/14 23:00:57 schwarze Exp $ */
+/*
+ * Copyright (c) 2009 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.
+ */
+
+/*
+ * This file defines the architecture token of the .Dt prologue macro.
+ * All architectures that your system supports (or the manuals of your
+ * system) should be included here.  The right-hand-side is the
+ * formatted output.
+ *
+ * Be sure to escape strings.
+ */
+
+LINE("alpha",          "Alpha")
+LINE("amd64",          "AMD64")
+LINE("amiga",          "Amiga")
+LINE("arc",            "ARC")
+LINE("arm",            "ARM")
+LINE("armish",         "ARMISH")
+LINE("aviion",         "AViiON")
+LINE("hp300",          "HP300")
+LINE("hppa",           "HPPA")
+LINE("hppa64",         "HPPA64")
+LINE("i386",           "i386")
+LINE("landisk",                "LANDISK")
+LINE("luna88k",                "Luna88k")
+LINE("mac68k",         "Mac68k")
+LINE("macppc",         "MacPPC")
+LINE("mvme68k",                "MVME68k")
+LINE("mvme88k",                "MVME88k")
+LINE("mvmeppc",                "MVMEPPC")
+LINE("pmax",           "PMAX")
+LINE("sgi",            "SGI")
+LINE("socppc",         "SOCPPC")
+LINE("sparc",          "SPARC")
+LINE("sparc64",                "SPARC64")
+LINE("sun3",           "Sun3")
+LINE("vax",            "VAX")
+LINE("zaurus",         "Zaurus")
diff --git a/usr.bin/mandoc/att.c b/usr.bin/mandoc/att.c
new file mode 100644 (file)
index 0000000..c3020c6
--- /dev/null
@@ -0,0 +1,32 @@
+/*     $Id: att.c,v 1.2 2009/06/14 23:00:57 schwarze Exp $ */
+/*
+ * Copyright (c) 2009 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 <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 (file)
index 0000000..c0efe3d
--- /dev/null
@@ -0,0 +1,37 @@
+/*     $Id: att.in,v 1.2 2009/06/14 23:00:57 schwarze Exp $ */
+/*
+ * Copyright (c) 2009 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.
+ */
+
+/*
+ * This file defines the AT&T versions of the .At macro.  This probably
+ * isn't going to change.  The right-hand side is the formatted string.
+ *
+ * Be sure to escape strings.
+ */
+
+LINE("v1",             "Version 1 AT&T UNIX")
+LINE("v2",             "Version 2 AT&T UNIX")
+LINE("v3",             "Version 3 AT&T UNIX")
+LINE("v4",             "Version 4 AT&T UNIX")
+LINE("v5",             "Version 5 AT&T UNIX")
+LINE("v6",             "Version 6 AT&T UNIX")
+LINE("v7",             "Version 7 AT&T UNIX")
+LINE("32v",            "Version 32V AT&T UNIX")
+LINE("V",              "AT&T System V UNIX")
+LINE("V.1",            "AT&T System V.1 UNIX")
+LINE("V.2",            "AT&T System V.2 UNIX")
+LINE("V.3",            "AT&T System V.3 UNIX")
+LINE("V.4",            "AT&T System V.4 UNIX")
diff --git a/usr.bin/mandoc/chars.c b/usr.bin/mandoc/chars.c
new file mode 100644 (file)
index 0000000..1190fda
--- /dev/null
@@ -0,0 +1,204 @@
+/*     $Id: chars.c,v 1.2 2009/10/19 09:56:35 schwarze Exp $ */
+/*
+ * Copyright (c) 2009 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 <assert.h>
+#include <err.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "chars.h"
+
+#define        PRINT_HI         126
+#define        PRINT_LO         32
+
+struct ln {
+       struct ln        *next;
+       const char       *code;
+       const char       *ascii;
+       const char       *html;
+       size_t            codesz;
+       size_t            asciisz;
+       size_t            htmlsz;
+       int               type;
+#define        CHARS_CHAR       (1 << 0)
+#define        CHARS_STRING     (1 << 1)
+#define CHARS_BOTH      (0x03)
+};
+
+#define        LINES_MAX         351
+
+#define CHAR(w, x, y, z, a, b) \
+       { NULL, (w), (y), (a), (x), (z), (b), CHARS_CHAR },
+#define STRING(w, x, y, z, a, b) \
+       { NULL, (w), (y), (a), (x), (z), (b), CHARS_STRING },
+#define BOTH(w, x, y, z, a, b) \
+       { NULL, (w), (y), (a), (x), (z), (b), CHARS_BOTH },
+
+static struct ln lines[LINES_MAX] = {
+#include "chars.in"
+};
+
+struct tbl {
+       enum chars        type;
+       struct ln       **htab;
+};
+
+static inline int        match(const struct ln *,
+                               const char *, size_t, int);
+static const char       *find(struct tbl *, const char *,
+                               size_t, size_t *, int);
+
+
+void
+chars_free(void *arg)
+{
+       struct tbl      *tab;
+
+       tab = (struct tbl *)arg;
+
+       free(tab->htab);
+       free(tab);
+}
+
+
+void *
+chars_init(enum chars type)
+{
+       struct tbl       *tab;
+       struct ln       **htab;
+       struct ln        *pp;
+       int               i, hash;
+
+       /*
+        * Constructs a very basic chaining hashtable.  The hash routine
+        * is simply the integral value of the first character.
+        * Subsequent entries are chained in the order they're processed
+        * (they're in-line re-ordered during lookup).
+        */
+
+       if (NULL == (tab = malloc(sizeof(struct tbl))))
+               err(1, "malloc");
+       tab->type = type;
+
+       htab = calloc(PRINT_HI - PRINT_LO + 1, sizeof(struct ln **));
+       if (NULL == htab)
+               err(1, "malloc");
+
+       for (i = 0; i < LINES_MAX; i++) {
+               hash = (int)lines[i].code[0] - PRINT_LO;
+
+               if (NULL == (pp = htab[hash])) {
+                       htab[hash] = &lines[i];
+                       continue;
+               }
+
+               for ( ; pp->next; pp = pp->next)
+                       /* Scan ahead. */ ;
+               pp->next = &lines[i];
+       }
+
+       tab->htab = htab;
+       return(tab);
+}
+
+
+const char *
+chars_a2ascii(void *arg, const char *p, size_t sz, size_t *rsz)
+{
+
+       return(find((struct tbl *)arg, p, sz, rsz, CHARS_CHAR));
+}
+
+
+const char *
+chars_a2res(void *arg, const char *p, size_t sz, size_t *rsz)
+{
+
+       return(find((struct tbl *)arg, p, sz, rsz, CHARS_STRING));
+}
+
+
+static const char *
+find(struct tbl *tab, const char *p, size_t sz, size_t *rsz, int type)
+{
+       struct ln        *pp, *prev;
+       struct ln       **htab;
+       int               hash;
+
+       assert(p);
+       assert(sz > 0);
+
+       if (p[0] < PRINT_LO || p[0] > PRINT_HI)
+               return(NULL);
+
+       /*
+        * Lookup the symbol in the symbol hash.  See ascii2htab for the
+        * hashtable specs.  This dynamically re-orders the hash chain
+        * to optimise for repeat hits.
+        */
+
+       hash = (int)p[0] - PRINT_LO;
+       htab = tab->htab;
+
+       if (NULL == (pp = htab[hash]))
+               return(NULL);
+
+       if (NULL == pp->next) {
+               if ( ! match(pp, p, sz, type))
+                       return(NULL);
+
+               if (CHARS_HTML == tab->type) {
+                       *rsz = pp->htmlsz;
+                       return(pp->html);
+               }
+               *rsz = pp->asciisz;
+               return(pp->ascii);
+       }
+
+       for (prev = NULL; pp; pp = pp->next) {
+               if ( ! match(pp, p, sz, type)) {
+                       prev = pp;
+                       continue;
+               }
+
+               if (prev) {
+                       prev->next = pp->next;
+                       pp->next = htab[hash];
+                       htab[hash] = pp;
+               }
+
+               if (CHARS_HTML == tab->type) {
+                       *rsz = pp->htmlsz;
+                       return(pp->html);
+               }
+               *rsz = pp->asciisz;
+               return(pp->ascii);
+       }
+
+       return(NULL);
+}
+
+
+static inline int
+match(const struct ln *ln, const char *p, size_t sz, int type)
+{
+
+       if ( ! (ln->type & type))
+               return(0);
+       if (ln->codesz != sz)
+               return(0);
+       return(0 == strncmp(ln->code, p, sz));
+}
diff --git a/usr.bin/mandoc/chars.h b/usr.bin/mandoc/chars.h
new file mode 100644 (file)
index 0000000..fa1608a
--- /dev/null
@@ -0,0 +1,34 @@
+/*     $Id: chars.h,v 1.1 2009/10/19 09:16:58 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009 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.
+ */
+#ifndef CHARS_H
+#define CHARS_H
+
+__BEGIN_DECLS
+
+enum   chars {
+       CHARS_ASCII,
+       CHARS_HTML
+};
+
+void            *chars_init(enum chars);
+const char      *chars_a2ascii(void *, const char *, size_t, size_t *);
+const char      *chars_a2res(void *, const char *, size_t, size_t *);
+void             chars_free(void *);
+
+__END_DECLS
+
+#endif /*!CHARS_H*/
diff --git a/usr.bin/mandoc/chars.in b/usr.bin/mandoc/chars.in
new file mode 100644 (file)
index 0000000..2f4bc0d
--- /dev/null
@@ -0,0 +1,418 @@
+/*     $Id: chars.in,v 1.2 2009/10/19 09:56:35 schwarze Exp $ */
+/*
+ * Copyright (c) 2009 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.
+ */
+
+/*
+ * The ASCII translation tables.  STRING corresponds to predefined
+ * strings (cf. mdoc_samples.7 and tmac/mdoc/doc-nroff).  CHAR
+ * corresponds to special characters (cf. groff_char.7).  BOTH contains
+ * sequences that are equivalent in both STRING and CHAR.
+ *
+ * Either way, the left-hand side corresponds to the input sequence (\x,
+ * \(xx, \*(xx and so on) whose length is listed second element.  The
+ * right-hand side is what's produced by the front-end, with the fourth
+ * element being its length.
+ *
+ * XXX - C-escape strings!
+ * XXX - update LINES_MAX if adding more!
+ */
+
+/* Spacing. */
+CHAR("c",                      1,      "",             0,      "",             0)
+CHAR("0",                      1,      " ",            1,      "&#8194;",      7)
+CHAR(" ",                      1,      " ",            1,      "&#8194;",      7)
+CHAR("~",                      1,      " ",            1,      "&#160;",       6)
+CHAR("%",                      1,      "",             0,      "",             0)
+CHAR("&",                      1,      "",             0,      "",             0)
+CHAR("^",                      1,      "",             0,      "",             0)
+CHAR("|",                      1,      "",             0,      "",             0)
+
+/* Accents. */
+CHAR("a\"",                    2,      "\"",           1,      "&#779;",       6)
+CHAR("a-",                     2,      "-",            1,      "&#175;",       6)
+CHAR("a.",                     2,      ".",            1,      "&#729;",       6)
+CHAR("a^",                     2,      "^",            1,      "&#770;",       6)
+CHAR("\'",                     1,      "\'",           1,      "&#769;",       6)
+BOTH("aa",                     2,      "\'",           1,      "&#769;",       6)
+BOTH("ga",                     2,      "`",            1,      "&#768;",       6)
+CHAR("`",                      1,      "`",            1,      "&#768;",       6)
+CHAR("ab",                     2,      "`",            1,      "&#774;",       6)
+CHAR("ac",                     2,      ",",            1,      "&#807;",       6)
+CHAR("ad",                     2,      "\"",           1,      "&#776;",       6)
+CHAR("ah",                     2,      "v",            1,      "&#711;",       6)
+CHAR("ao",                     2,      "o",            1,      "&#730;",       6)
+CHAR("a~",                     2,      "~",            1,      "&#771;",       6)
+CHAR("ho",                     2,      ",",            1,      "&#808;",       6)
+CHAR("ha",                     2,      "^",            1,      "^",            1)
+CHAR("ti",                     2,      "~",            1,      "~",            1)
+
+/* Quotes. */
+CHAR("Bq",                     2,      ",,",           2,      "&#8222;",      7)
+CHAR("bq",                     2,      ",",            1,      "&#8218;",      7)
+BOTH("lq",                     2,      "``",           2,      "&#8220;",      7)
+BOTH("rq",                     2,      "\'\'",         2,      "&#8221;",      7)
+CHAR("oq",                     2,      "`",            1,      "&#8216;",      7)
+CHAR("cq",                     2,      "\'",           1,      "&#8217;",      7)
+CHAR("aq",                     2,      "\'",           1,      "\'",           1)
+CHAR("dq",                     2,      "\"",           1,      "\"",           1)
+CHAR("Fo",                     2,      "<<",           2,      "&#171;",       6)
+CHAR("Fc",                     2,      ">>",           2,      "&#187;",       6)
+CHAR("fo",                     2,      "<",            1,      "&#8249;",      7)
+CHAR("fc",                     2,      ">",            1,      "&#8250;",      7)
+
+/* Brackets. */
+CHAR("lB",                     2,      "[",            1,      "[",            1)
+CHAR("rB",                     2,      "]",            1,      "]",            1)
+CHAR("lC",                     2,      "{",            1,      "{",            1)
+CHAR("rC",                     2,      "}",            1,      "}",            1)
+CHAR("la",                     2,      "<",            1,      "&#10216;",     8)
+CHAR("ra",                     2,      ">",            1,      "&#10217;",     8)
+CHAR("bv",                     2,      "|",            1,      "&#9130;",      7)
+CHAR("braceex",                        7,      "|",            1,      "&#9130;",      7)
+CHAR("bracketlefttp",          13,     "|",            1,      "&#9121;",      7)
+CHAR("bracketleftbp",          13,     "|",            1,      "&#9123;",      7)
+CHAR("bracketleftex",          13,     "|",            1,      "&#9122;",      7)
+CHAR("bracketrighttp",         14,     "|",            1,      "&#9124;",      7)
+CHAR("bracketrightbp",         14,     "|",            1,      "&#9126;",      7)
+CHAR("bracketrightex",         14,     "|",            1,      "&#9125;",      7)
+CHAR("lt",                     2,      ",-",           2,      "&#9127;",      7)
+CHAR("bracelefttp",            11,     ",-",           2,      "&#9127;",      7)
+CHAR("lk",                     2,      "{",            1,      "&#9128;",      7)
+CHAR("braceleftmid",           12,     "{",            1,      "&#9128;",      7)
+CHAR("lb",                     2,      ",-",           2,      "&#9129;",      7)
+CHAR("braceleftbp",            11,     "`-",           2,      "&#9129;",      7)
+CHAR("braceleftex",            11,     "|",            1,      "&#9130;",      7)
+CHAR("rt",                     2,      "-.",           2,      "&#9131;",      7)
+CHAR("bracerighttp",           12,     "-.",           2,      "&#9131;",      7)
+CHAR("rk",                     2,      "}",            1,      "&#9132;",      7)
+CHAR("bracerightmid",          13,     "}",            1,      "&#9132;",      7)
+CHAR("rb",                     2,      "-\'",          2,      "&#9133;",      7)
+CHAR("bracerightbp",           12,     "-\'",          2,      "&#9133;",      7)
+CHAR("bracerightex",           12,     "|",            1,      "&#9130;",      7)
+CHAR("parenlefttp",            11,     "/",            1,      "&#9115;",      7)
+CHAR("parenleftbp",            11,     "\\",           1,      "&#9117;",      7)
+CHAR("parenleftex",            11,     "|",            1,      "&#9116;",      7)
+CHAR("parenrighttp",           12,     "\\",           1,      "&#9118;",      7)
+CHAR("parenrightbp",           12,     "/",            1,      "&#9120;",      7)
+CHAR("parenrightex",           12,     "|",            1,      "&#9119;",      7)
+
+/* Greek characters. */
+CHAR("*A",                     2,      "A",            1,      "&#913;",       6)
+CHAR("*B",                     2,      "B",            1,      "&#914;",       6)
+CHAR("*G",                     2,      "|",            1,      "&#915;",       6)
+CHAR("*D",                     2,      "/\\",          2,      "&#916;",       6)
+CHAR("*E",                     2,      "E",            1,      "&#917;",       6)
+CHAR("*Z",                     2,      "Z",            1,      "&#918;",       6)
+CHAR("*Y",                     2,      "H",            1,      "&#919;",       6)
+CHAR("*H",                     2,      "O",            1,      "&#920;",       6)
+CHAR("*I",                     2,      "I",            1,      "&#921;",       6)
+CHAR("*K",                     2,      "K",            1,      "&#922;",       6)
+CHAR("*L",                     2,      "/\\",          2,      "&#923;",       6)
+CHAR("*M",                     2,      "M",            1,      "&#924;",       6)
+CHAR("*N",                     2,      "N",            1,      "&#925;",       6)
+CHAR("*C",                     2,      "H",            1,      "&#926;",       6)
+CHAR("*O",                     2,      "O",            1,      "&#927;",       6)
+CHAR("*P",                     2,      "TT",           2,      "&#928;",       6)
+CHAR("*R",                     2,      "P",            1,      "&#929;",       6)
+CHAR("*S",                     2,      ">",            1,      "&#931;",       6)
+CHAR("*T",                     2,      "T",            1,      "&#932;",       6)
+CHAR("*U",                     2,      "Y",            1,      "&#933;",       6)
+CHAR("*F",                     2,      "O_",           1,      "&#934;",       6)
+CHAR("*X",                     2,      "X",            1,      "&#935;",       6)
+CHAR("*Q",                     2,      "Y",            1,      "&#936;",       6)
+CHAR("*W",                     2,      "O",            1,      "&#937;",       6)
+CHAR("*a",                     2,      "a",            1,      "&#945;",       6)
+CHAR("*b",                     2,      "B",            1,      "&#946;",       6)
+CHAR("*g",                     2,      "y",            1,      "&#947;",       6)
+CHAR("*d",                     2,      "d",            1,      "&#948;",       6)
+CHAR("*e",                     2,      "e",            1,      "&#949;",       6)
+CHAR("*z",                     2,      "C",            1,      "&#950;",       6)
+CHAR("*y",                     2,      "n",            1,      "&#951;",       6)
+CHAR("*h",                     2,      "0",            1,      "&#952;",       6)
+CHAR("*i",                     2,      "i",            1,      "&#953;",       6)
+CHAR("*k",                     2,      "k",            1,      "&#954;",       6)
+CHAR("*l",                     2,      "\\",           1,      "&#955;",       6)
+CHAR("*m",                     2,      "u",            1,      "&#956;",       6)
+CHAR("*n",                     2,      "v",            1,      "&#957;",       6)
+CHAR("*c",                     2,      "E",            1,      "&#958;",       6)
+CHAR("*o",                     2,      "o",            1,      "&#959;",       6)
+CHAR("*p",                     2,      "n",            1,      "&#960;",       6)
+CHAR("*r",                     2,      "p",            1,      "&#961;",       6)
+CHAR("*s",                     2,      "o",            1,      "&#963;",       6)
+CHAR("*t",                     2,      "t",            1,      "&#964;",       6)
+CHAR("*u",                     2,      "u",            1,      "&#965;",       6)
+CHAR("*f",                     2,      "o",            1,      "&#981;",       6)
+CHAR("*x",                     2,      "x",            1,      "&#967;",       6)
+CHAR("*q",                     2,      "u",            1,      "&#968;",       6)
+CHAR("*w",                     2,      "w",            1,      "&#969;",       6)
+CHAR("+h",                     2,      "0",            1,      "&#977;",       6)
+CHAR("+f",                     2,      "o",            1,      "&#966;",       6)
+CHAR("+p",                     2,      "w",            1,      "&#982;",       6)
+CHAR("+e",                     2,      "e",            1,      "&#1013;",      7)
+CHAR("ts",                     2,      "s",            1,      "&#962;",       6)
+
+/* Accented letters. */
+CHAR(",C",                     2,      "C",            1,      "&#199;",       6)
+CHAR(",c",                     2,      "c",            1,      "&#231;",       6)
+CHAR("/L",                     2,      "L",            1,      "&#321;",       6)
+CHAR("/O",                     2,      "O",            1,      "&#216;",       6)
+CHAR("/l",                     2,      "l",            1,      "&#322;",       6)
+CHAR("/o",                     2,      "o",            1,      "&#248;",       6)
+CHAR("oA",                     2,      "A",            1,      "&#197;",       6)
+CHAR("oa",                     2,      "a",            1,      "&#229;",       6)
+CHAR(":A",                     2,      "A",            1,      "&#196;",       6)
+CHAR(":E",                     2,      "E",            1,      "&#203;",       6)
+CHAR(":I",                     2,      "I",            1,      "&#207;",       6)
+CHAR(":O",                     2,      "O",            1,      "&#214;",       6)
+CHAR(":U",                     2,      "U",            1,      "&#220;",       6)
+CHAR(":a",                     2,      "a",            1,      "&#228;",       6)
+CHAR(":e",                     2,      "e",            1,      "&#235;",       6)
+CHAR(":i",                     2,      "i",            1,      "&#239;",       6)
+CHAR(":o",                     2,      "o",            1,      "&#245;",       6)
+CHAR(":u",                     2,      "u",            1,      "&#252;",       6)
+CHAR(":y",                     2,      "y",            1,      "&#255;",       6)
+CHAR("\'A",                    2,      "A",            1,      "&#193;",       6)
+CHAR("\'E",                    2,      "E",            1,      "&#201;",       6)
+CHAR("\'I",                    2,      "I",            1,      "&#205;",       6)
+CHAR("\'O",                    2,      "O",            1,      "&#211;",       6)
+CHAR("\'U",                    2,      "U",            1,      "&#218;",       6)
+CHAR("\'a",                    2,      "a",            1,      "&#225;",       6)
+CHAR("\'e",                    2,      "e",            1,      "&#233;",       6)
+CHAR("\'i",                    2,      "i",            1,      "&#237;",       6)
+CHAR("\'o",                    2,      "o",            1,      "&#243;",       6)
+CHAR("\'u",                    2,      "u",            1,      "&#250;",       6)
+CHAR("^A",                     2,      "A",            1,      "&#194;",       6)
+CHAR("^E",                     2,      "E",            1,      "&#202;",       6)
+CHAR("^I",                     2,      "I",            1,      "&#206;",       6)
+CHAR("^O",                     2,      "O",            1,      "&#212;",       6)
+CHAR("^U",                     2,      "U",            1,      "&#219;",       6)
+CHAR("^a",                     2,      "a",            1,      "&#226;",       6)
+CHAR("^e",                     2,      "e",            1,      "&#234;",       6)
+CHAR("^i",                     2,      "i",            1,      "&#238;",       6)
+CHAR("^o",                     2,      "o",            1,      "&#244;",       6)
+CHAR("^u",                     2,      "u",            1,      "&#251;",       6)
+CHAR("`A",                     2,      "A",            1,      "&#192;",       6)
+CHAR("`E",                     2,      "E",            1,      "&#200;",       6)
+CHAR("`I",                     2,      "I",            1,      "&#204;",       6)
+CHAR("`O",                     2,      "O",            1,      "&#210;",       6)
+CHAR("`U",                     2,      "U",            1,      "&#217;",       6)
+CHAR("`a",                     2,      "a",            1,      "&#224;",       6)
+CHAR("`e",                     2,      "e",            1,      "&#232;",       6)
+CHAR("`i",                     2,      "i",            1,      "&#236;",       6)
+CHAR("`o",                     2,      "o",            1,      "&#242;",       6)
+CHAR("`u",                     2,      "u",            1,      "&#249;",       6)
+CHAR("~A",                     2,      "A",            1,      "&#195;",       6)
+CHAR("~N",                     2,      "N",            1,      "&#209;",       6)
+CHAR("~O",                     2,      "O",            1,      "&#213;",       6)
+CHAR("~a",                     2,      "a",            1,      "&#227;",       6)
+CHAR("~n",                     2,      "n",            1,      "&#241;",       6)
+CHAR("~o",                     2,      "o",            1,      "&#245;",       6)
+
+/* Arrows and lines. */
+CHAR("<-",                     2,      "<-",           2,      "&#8592;",      7)
+CHAR("->",                     2,      "->",           2,      "&#8594;",      7)
+CHAR("<>",                     2,      "<>",           2,      "&#8596;",      7)
+CHAR("da",                     2,      "v",            1,      "&#8595;",      7)
+BOTH("ua",                     2,      "^",            1,      "&#8593;",      7)
+BOTH("va",                     2,      "^v",           2,      "&#8597;",      7)
+CHAR("lA",                     2,      "<=",           2,      "&#8656;",      7)
+CHAR("rA",                     2,      "=>",           2,      "&#8658;",      7)
+CHAR("hA",                     2,      "<=>",          3,      "&#8660;",      7)
+CHAR("dA",                     2,      "v",            1,      "&#8659;",      7)
+CHAR("uA",                     2,      "^",            1,      "&#8657;",      7)
+CHAR("vA",                     2,      "^=v",          3,      "&#8661;",      7)
+
+/* Logic. */
+CHAR("AN",                     2,      "^",            1,      "&#8743;",      7)
+CHAR("OR",                     2,      "v",            1,      "&#8744;",      7)
+CHAR("no",                     2,      "~",            1,      "&#172;",       6)
+CHAR("tno",                    3,      "~",            1,      "&#172;",       6)
+CHAR("te",                     2,      "3",            1,      "&#8707;",      7)
+CHAR("fa",                     2,      "V",            1,      "&#8704;",      7)
+CHAR("st",                     2,      "-)",           2,      "&#8715;",      7)
+CHAR("tf",                     2,      ".:.",          3,      "&#8756;",      7)
+CHAR("3d",                     2,      ".:.",          3,      "&#8756;",      7)
+CHAR("or",                     2,      "|",            1,      "|",            1)
+
+/* Mathematicals. */
+CHAR("pl",                     2,      "+",            1,      "&#43;",        5)
+CHAR("mi",                     2,      "-",            1,      "&#8722;",      7)
+CHAR("-",                      1,      "-",            1,      "-",            1)
+CHAR("-+",                     2,      "-+",           2,      "&#8723;",      7)
+CHAR("+-",                     2,      "+-",           2,      "&#177;",       6)
+CHAR("t+-",                    3,      "+-",           2,      "&#177;",       6)
+CHAR("pc",                     2,      ".",            1,      "&#183;",       6)
+CHAR("md",                     2,      ".",            1,      "&#8901;",      7)
+CHAR("mu",                     2,      "x",            1,      "&#215;",       6)
+CHAR("tmu",                    3,      "x",            1,      "&#215;",       6)
+CHAR("c*",                     2,      "x",            1,      "&#8855;",      7)
+CHAR("c+",                     2,      "+",            1,      "&#8853;",      7)
+CHAR("di",                     2,      "-:-",          3,      "&#247;",       6)
+CHAR("tdi",                    3,      "-:-",          3,      "&#247;",       6)
+CHAR("f/",                     2,      "/",            1,      "&#8260;",      7)
+CHAR("**",                     2,      "*",            1,      "&#8727;",      7)
+BOTH("<=",                     2,      "<=",           2,      "&#8804;",      7)
+BOTH(">=",                     2,      ">=",           2,      "&#8805;",      7)
+CHAR("<<",                     2,      "<<",           2,      "&#8810;",      7)
+CHAR(">>",                     2,      ">>",           2,      "&#8811;",      7)
+CHAR("eq",                     2,      "=",            1,      "&#61;",        5)
+CHAR("!=",                     2,      "!=",           2,      "&#8800;",      7)
+CHAR("==",                     2,      "==",           2,      "&#8801;",      7)
+CHAR("ne",                     2,      "!==",          3,      "&#8802;",      7)
+CHAR("=~",                     2,      "=~",           2,      "&#8773;",      7)
+CHAR("-~",                     2,      "-~",           2,      "&#8771;",      7)
+CHAR("ap",                     2,      "~",            1,      "&#8764;",      7)
+CHAR("~~",                     2,      "~~",           2,      "&#8776;",      7)
+CHAR("~=",                     2,      "~=",           2,      "&#8780;",      7)
+CHAR("pt",                     2,      "oc",           2,      "&#8733;",      7)
+CHAR("es",                     2,      "{}",           2,      "&#8709;",      7)
+CHAR("mo",                     2,      "E",            1,      "&#8712;",      7)
+CHAR("nm",                     2,      "!E",           2,      "&#8713;",      7)
+CHAR("sb",                     2,      "(=",           2,      "&#8834;",      7)
+CHAR("nb",                     2,      "(!=",          3,      "&#8836;",      7)
+CHAR("sp",                     2,      "=)",           2,      "&#8835;",      7)
+CHAR("nc",                     2,      "!=)",          3,      "&#8837;",      7)
+CHAR("ib",                     2,      "(=",           2,      "&#8838;",      7)
+CHAR("ip",                     2,      "=)",           2,      "&#8839;",      7)
+CHAR("ca",                     2,      "(^)",          3,      "&#8745;",      7)
+CHAR("cu",                     2,      "U",            1,      "&#8746;",      7)
+CHAR("/_",                     2,      "/_",           2,      "&#8736;",      7)
+CHAR("pp",                     2,      "_|_",          3,      "&#8869;",      7)
+CHAR("is",                     2,      "I",            1,      "&#8747;",      7)
+CHAR("integral",               8,      "I",            1,      "&#8747;",      7)
+CHAR("sum",                    3,      "E",            1,      "&#8721;",      7)
+CHAR("product",                        7,      "TT",           2,      "&#8719;",      7)
+CHAR("coproduct",              9,      "U",            1,      "&#8720;",      7)
+CHAR("gr",                     2,      "V",            1,      "&#8711;",      7)
+CHAR("sr",                     2,      "\\/",          2,      "&#8730;",      7)
+CHAR("sqrt",                   4,      "\\/",          2,      "&#8730;",      7)
+CHAR("lc",                     2,      "|~",           2,      "&#8968;",      7)
+CHAR("rc",                     2,      "~|",           2,      "&#8969;",      7)
+CHAR("lf",                     2,      "|_",           2,      "&#8970;",      7)
+CHAR("rf",                     2,      "_|",           2,      "&#8971;",      7)
+CHAR("if",                     2,      "oo",           2,      "&#8734;",      7)
+CHAR("Ah",                     2,      "N",            1,      "&#8501;",      7)
+CHAR("Im",                     2,      "I",            1,      "&#8465;",      7)
+CHAR("Re",                     2,      "R",            1,      "&#8476;",      7)
+CHAR("pd",                     2,      "a",            1,      "&#8706;",      7)
+CHAR("-h",                     2,      "/h",           2,      "&#8463;",      7)
+
+/* Ligatures. */
+CHAR("ff",                     2,      "ff",           2,      "&#64256;",     8)
+CHAR("fi",                     2,      "fi",           2,      "&#64257;",     8)
+CHAR("fl",                     2,      "fl",           2,      "&#64258;",     8)
+CHAR("Fi",                     2,      "ffi",          3,      "&#64259;",     8)
+CHAR("Fl",                     2,      "ffl",          3,      "&#64260;",     8)
+CHAR("AE",                     2,      "AE",           2,      "&#198;",       6)
+CHAR("ae",                     2,      "ae",           2,      "&#230;",       6)
+CHAR("OE",                     2,      "OE",           2,      "&#338;",       6)
+CHAR("oe",                     2,      "oe",           2,      "&#339;",       6)
+CHAR("ss",                     2,      "ss",           2,      "&#223;",       6)
+CHAR("IJ",                     2,      "IJ",           2,      "&#306;",       6)
+CHAR("ij",                     2,      "ij",           2,      "&#307;",       6)
+
+/* Special letters. */
+CHAR("-D",                     2,      "D",            1,      "&#208;",       6)
+CHAR("Sd",                     2,      "o",            1,      "&#240;",       6)
+CHAR("TP",                     2,      "b",            1,      "&#222;",       6)
+CHAR("Tp",                     2,      "b",            1,      "&#254;",       6)
+CHAR(".i",                     2,      "i",            1,      "&#305;",       6)
+CHAR(".j",                     2,      "j",            1,      "&#567;",       6)
+
+/* Currency. */
+CHAR("Do",                     2,      "$",            1,      "$",            1)
+CHAR("ct",                     2,      "c",            1,      "&#162;",       6)
+CHAR("Eu",                     2,      "EUR",          3,      "&#8364;",      7)
+CHAR("eu",                     2,      "EUR",          3,      "&#8364;",      7)
+CHAR("Ye",                     2,      "Y",            1,      "&#165;",       6)
+CHAR("Po",                     2,      "L",            1,      "&#163;",       6)
+CHAR("Cs",                     2,      "x",            1,      "&#164;",       6)
+CHAR("Fn",                     2,      "f",            1,      "&#402;",       6)
+
+/* Old style. */
+STRING("Am",                   2,      "&",            1,      "&amp;",        5)
+STRING("Ba",                   2,      "|",            1,      "|",            1)
+STRING("Ge",                   2,      ">=",           2,      "&#8805;",      7)
+STRING("Gt",                   2,      ">",            1,      "&gt;",         4)
+STRING("If",                   2,      "infinity",     8,      "infinity",     8)
+STRING("Le",                   2,      "<=",           2,      "&#8804;",      7)
+STRING("Lq",                   2,      "``",           2,      "&#8220;",      7)
+STRING("Lt",                   2,      "<",            1,      "&lt;",         4)
+STRING("Na",                   2,      "NaN",          3,      "NaN",          3)
+STRING("Ne",                   2,      "!=",           2,      "&#8800;",      7)
+STRING("Pi",                   2,      "pi",           2,      "&#960;",       6)
+STRING("Pm",                   2,      "+-",           2,      "&#177;",       6)
+STRING("R",                    1,      "(R)",          3,      "&#174;",       6)
+STRING("Rq",                   2,      "\'\'",         2,      "&#8221;",      7)
+STRING("Tm",                   2,      "tm",           2,      "&#8482;",      7)
+STRING("left-bracket",         12,     "[",            1,      "[",            1)
+STRING("left-parenthesis",     16,     "(",            1,      "(",            1)
+STRING("left-singlequote",     16,     "`",            1,      "&#8216;",      7)
+STRING("lp",                   2,      "(",            1,      "(",            1)
+STRING("q",                    1,      "\"",           1,      "&quot;",       6)
+STRING("quote-left",           10,     "`",            1,      "&#8216;",      7)
+STRING("quote-right",          11,     "\'",           1,      "&#8217;",      7)
+STRING("right-bracket",                13,     "]",            1,      "]",            1)
+STRING("right-parenthesis",    17,     ")",            1,      ")",            1)
+STRING("right-singlequote",    17,     "\'",           1,      "&#8217;",      7)
+STRING("rp",                   2,      ")",            1,      ")",            1)
+
+/* Lines. */
+CHAR("ba",                     2,      "|",            1,      "&#124;",       6)
+CHAR("br",                     2,      "|",            1,      "&#9474;",      7)
+CHAR("ul",                     2,      "_",            1,      "&#95;",        5)
+CHAR("rl",                     2,      "-",            1,      "&#8254;",      7)
+CHAR("bb",                     2,      "|",            1,      "&#166;",       6)
+CHAR("sl",                     2,      "/",            1,      "&#47;",        5)
+CHAR("rs",                     2,      "\\",           1,      "&#92;",        5)
+
+/* Text markers. */
+CHAR("ci",                     2,      "o",            1,      "&#9675;",      7)
+CHAR("bu",                     2,      "o",            1,      "&#8226;",      7)
+CHAR("dd",                     2,      "=",            1,      "&#8225;",      7)
+CHAR("dg",                     2,      "-",            1,      "&#8224;",      7)
+CHAR("lz",                     2,      "<>",           2,      "&#9674;",      7)
+CHAR("sq",                     2,      "[]",           2,      "&#9633;",      7)
+CHAR("ps",                     2,      "9|",           2,      "&#182;",       6)
+CHAR("sc",                     2,      "S",            1,      "&#167;",       6)
+CHAR("lh",                     2,      "<=",           2,      "&#9756;",      7)
+CHAR("rh",                     2,      "=>",           2,      "&#9758;",      7)
+CHAR("at",                     2,      "@",            1,      "&#64;",        5)
+CHAR("sh",                     2,      "#",            1,      "&#35;",        5)
+CHAR("CR",                     2,      "_|",           2,      "&#8629;",      7)
+CHAR("OK",                     2,      "\\/",          2,      "&#10003;",     8)
+
+/* Legal symbols. */
+CHAR("co",                     2,      "(C)",          3,      "&#169;",       6)
+CHAR("rg",                     2,      "(R)",          3,      "&#174;",       6)
+CHAR("tm",                     2,      "tm",           2,      "&#8482;",      7)
+
+/* Punctuation. */
+CHAR(".",                      1,      ".",            1,      ".",            1)
+CHAR("r!",                     2,      "i",            1,      "&#161;",       6)
+CHAR("r?",                     2,      "c",            1,      "&#191;",       6)
+CHAR("em",                     2,      "--",           2,      "&#8212;",      7)
+CHAR("en",                     2,      "-",            1,      "&#8211;",      7)
+CHAR("hy",                     2,      "-",            1,      "&#8208;",      7)
+CHAR("\\",                     1,      "\\",           1,      "\\",           1)
+CHAR("e",                      1,      "\\",           1,      "\\",           1)
+
+/* Units. */
+CHAR("de",                     2,      "o",            1,      "&#176;",       6)
+CHAR("%0",                     2,      "%o",           2,      "&#8240;",      7)
+CHAR("fm",                     2,      "\'",           1,      "&#8242;",      7)
+CHAR("sd",                     2,      "\"",           1,      "&#8243;",      7)
+CHAR("mc",                     2,      "mu",           2,      "&#181;",       6)
diff --git a/usr.bin/mandoc/html.c b/usr.bin/mandoc/html.c
new file mode 100644 (file)
index 0000000..6703cd0
--- /dev/null
@@ -0,0 +1,648 @@
+/*     $Id: html.c,v 1.1 2009/10/21 19:13:50 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009 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 <sys/queue.h>
+
+#include <assert.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "out.h"
+#include "chars.h"
+#include "html.h"
+#include "main.h"
+
+#define        UNCONST(a)      ((void *)(uintptr_t)(const void *)(a))
+
+#define        DOCTYPE         "-//W3C//DTD HTML 4.01//EN"
+#define        DTD             "http://www.w3.org/TR/html4/strict.dtd"
+
+struct htmldata {
+       const char       *name;
+       int               flags;
+#define        HTML_CLRLINE     (1 << 0)
+#define        HTML_NOSTACK     (1 << 1)
+};
+
+static const struct htmldata htmltags[TAG_MAX] = {
+       {"html",        HTML_CLRLINE}, /* TAG_HTML */
+       {"head",        HTML_CLRLINE}, /* TAG_HEAD */
+       {"body",        HTML_CLRLINE}, /* TAG_BODY */
+       {"meta",        HTML_CLRLINE | HTML_NOSTACK}, /* TAG_META */
+       {"title",       HTML_CLRLINE}, /* TAG_TITLE */
+       {"div",         HTML_CLRLINE}, /* TAG_DIV */
+       {"h1",          0}, /* TAG_H1 */
+       {"h2",          0}, /* TAG_H2 */
+       {"p",           HTML_CLRLINE}, /* TAG_P */
+       {"span",        0}, /* TAG_SPAN */
+       {"link",        HTML_CLRLINE | HTML_NOSTACK}, /* TAG_LINK */
+       {"br",          HTML_CLRLINE | HTML_NOSTACK}, /* TAG_LINK */
+       {"a",           0}, /* TAG_A */
+       {"table",       HTML_CLRLINE}, /* TAG_TABLE */
+       {"col",         HTML_CLRLINE | HTML_NOSTACK}, /* TAG_COL */
+       {"tr",          HTML_CLRLINE}, /* TAG_TR */
+       {"td",          HTML_CLRLINE}, /* TAG_TD */
+       {"li",          HTML_CLRLINE}, /* TAG_LI */
+       {"ul",          HTML_CLRLINE}, /* TAG_UL */
+       {"ol",          HTML_CLRLINE}, /* TAG_OL */
+       {"base",        HTML_CLRLINE | HTML_NOSTACK}, /* TAG_BASE */
+};
+
+static const char       *const htmlattrs[ATTR_MAX] = {
+       "http-equiv",
+       "content",
+       "name",
+       "rel",
+       "href",
+       "type",
+       "media",
+       "class",
+       "style",
+       "width",
+       "valign",
+       "target",
+       "id",
+};
+
+void *
+html_alloc(char *outopts)
+{
+       struct html     *h;
+       const char      *toks[4];
+       char            *v;
+
+       toks[0] = "style";
+       toks[1] = "man";
+       toks[2] = "includes";
+       toks[3] = NULL;
+
+       if (NULL == (h = calloc(1, sizeof(struct html))))
+               return(NULL);
+
+       SLIST_INIT(&h->tags);
+       SLIST_INIT(&h->ords);
+
+       if (NULL == (h->symtab = chars_init(CHARS_HTML))) {
+               free(h);
+               return(NULL);
+       }
+
+       while (outopts && *outopts)
+               switch (getsubopt(&outopts, UNCONST(toks), &v)) {
+               case (0):
+                       h->style = v;
+                       break;
+               case (1):
+                       h->base_man = v;
+                       break;
+               case (2):
+                       h->base_includes = v;
+                       break;
+               default:
+                       break;
+               }
+
+       return(h);
+}
+
+
+void
+html_free(void *p)
+{
+       struct tag      *tag;
+       struct ord      *ord;
+       struct html     *h;
+
+       h = (struct html *)p;
+
+       while ( ! SLIST_EMPTY(&h->ords)) {
+               ord = SLIST_FIRST(&h->ords);
+               SLIST_REMOVE_HEAD(&h->ords, entry);
+               free(ord);
+       }
+
+       while ( ! SLIST_EMPTY(&h->tags)) {
+               tag = SLIST_FIRST(&h->tags);
+               SLIST_REMOVE_HEAD(&h->tags, entry);
+               free(tag);
+       }
+
+       if (h->symtab)
+               chars_free(h->symtab);
+
+       free(h);
+}
+
+
+void
+print_gen_head(struct html *h)
+{
+       struct htmlpair  tag[4];
+
+       tag[0].key = ATTR_HTTPEQUIV;
+       tag[0].val = "Content-Type";
+       tag[1].key = ATTR_CONTENT;
+       tag[1].val = "text/html; charset=utf-8";
+       print_otag(h, TAG_META, 2, tag);
+
+       tag[0].key = ATTR_NAME;
+       tag[0].val = "resource-type";
+       tag[1].key = ATTR_CONTENT;
+       tag[1].val = "document";
+       print_otag(h, TAG_META, 2, tag);
+
+       if (h->style) {
+               tag[0].key = ATTR_REL;
+               tag[0].val = "stylesheet";
+               tag[1].key = ATTR_HREF;
+               tag[1].val = h->style;
+               tag[2].key = ATTR_TYPE;
+               tag[2].val = "text/css";
+               tag[3].key = ATTR_MEDIA;
+               tag[3].val = "all";
+               print_otag(h, TAG_LINK, 4, tag);
+       }
+}
+
+
+static void
+print_spec(struct html *h, const char *p, int len)
+{
+       const char      *rhs;
+       int              i;
+       size_t           sz;
+
+       rhs = chars_a2ascii(h->symtab, p, (size_t)len, &sz);
+
+       if (NULL == rhs)
+               return;
+       for (i = 0; i < (int)sz; i++)
+               putchar(rhs[i]);
+}
+
+
+static void
+print_res(struct html *h, const char *p, int len)
+{
+       const char      *rhs;
+       int              i;
+       size_t           sz;
+
+       rhs = chars_a2res(h->symtab, p, (size_t)len, &sz);
+
+       if (NULL == rhs)
+               return;
+       for (i = 0; i < (int)sz; i++)
+               putchar(rhs[i]);
+}
+
+
+static void
+print_escape(struct html *h, const char **p)
+{
+       int              j, type;
+       const char      *wp;
+
+       wp = *p;
+       type = 1;
+
+       if (0 == *(++wp)) {
+               *p = wp;
+               return;
+       }
+
+       if ('(' == *wp) {
+               wp++;
+               if (0 == *wp || 0 == *(wp + 1)) {
+                       *p = 0 == *wp ? wp : wp + 1;
+                       return;
+               }
+
+               print_spec(h, wp, 2);
+               *p = ++wp;
+               return;
+
+       } else if ('*' == *wp) {
+               if (0 == *(++wp)) {
+                       *p = wp;
+                       return;
+               }
+
+               switch (*wp) {
+               case ('('):
+                       wp++;
+                       if (0 == *wp || 0 == *(wp + 1)) {
+                               *p = 0 == *wp ? wp : wp + 1;
+                               return;
+                       }
+
+                       print_res(h, wp, 2);
+                       *p = ++wp;
+                       return;
+               case ('['):
+                       type = 0;
+                       break;
+               default:
+                       print_res(h, wp, 1);
+                       *p = wp;
+                       return;
+               }
+
+       } else if ('f' == *wp) {
+               if (0 == *(++wp)) {
+                       *p = wp;
+                       return;
+               }
+
+               switch (*wp) {
+               case ('B'):
+                       /* TODO */
+                       break;
+               case ('I'):
+                       /* TODO */
+                       break;
+               case ('P'):
+                       /* FALLTHROUGH */
+               case ('R'):
+                       /* TODO */
+                       break;
+               default:
+                       break;
+               }
+
+               *p = wp;
+               return;
+
+       } else if ('[' != *wp) {
+               print_spec(h, wp, 1);
+               *p = wp;
+               return;
+       }
+
+       wp++;
+       for (j = 0; *wp && ']' != *wp; wp++, j++)
+               /* Loop... */ ;
+
+       if (0 == *wp) {
+               *p = wp;
+               return;
+       }
+
+       if (type)
+               print_spec(h, wp - j, j);
+       else
+               print_res(h, wp - j, j);
+
+       *p = wp;
+}
+
+
+static void
+print_encode(struct html *h, const char *p)
+{
+
+       for (; *p; p++) {
+               if ('\\' == *p) {
+                       print_escape(h, &p);
+                       continue;
+               }
+               switch (*p) {
+               case ('<'):
+                       printf("&lt;");
+                       break;
+               case ('>'):
+                       printf("&gt;");
+                       break;
+               case ('&'):
+                       printf("&amp;");
+                       break;
+               default:
+                       putchar(*p);
+                       break;
+               }
+       }
+}
+
+
+struct tag *
+print_otag(struct html *h, enum htmltag tag,
+               int sz, const struct htmlpair *p)
+{
+       int              i;
+       struct tag      *t;
+
+       if ( ! (HTML_NOSTACK & htmltags[tag].flags)) {
+               if (NULL == (t = malloc(sizeof(struct tag))))
+                       err(EXIT_FAILURE, "malloc");
+               t->tag = tag;
+               SLIST_INSERT_HEAD(&h->tags, t, entry);
+       } else
+               t = NULL;
+
+       if ( ! (HTML_NOSPACE & h->flags))
+               if ( ! (HTML_CLRLINE & htmltags[tag].flags))
+                       printf(" ");
+
+       printf("<%s", htmltags[tag].name);
+       for (i = 0; i < sz; i++) {
+               printf(" %s=\"", htmlattrs[p[i].key]);
+               assert(p->val);
+               print_encode(h, p[i].val);
+               printf("\"");
+       }
+       printf(">");
+
+       h->flags |= HTML_NOSPACE;
+       if (HTML_CLRLINE & htmltags[tag].flags)
+               h->flags |= HTML_NEWLINE;
+       else
+               h->flags &= ~HTML_NEWLINE;
+
+       return(t);
+}
+
+
+/* ARGSUSED */
+static void
+print_ctag(struct html *h, enum htmltag tag)
+{
+
+       printf("</%s>", htmltags[tag].name);
+       if (HTML_CLRLINE & htmltags[tag].flags)
+               h->flags |= HTML_NOSPACE;
+       if (HTML_CLRLINE & htmltags[tag].flags)
+               h->flags |= HTML_NEWLINE;
+       else
+               h->flags &= ~HTML_NEWLINE;
+}
+
+
+/* ARGSUSED */
+void
+print_gen_doctype(struct html *h)
+{
+
+       printf("<!DOCTYPE HTML PUBLIC \"%s\" \"%s\">", DOCTYPE, DTD);
+}
+
+
+void
+print_text(struct html *h, const char *p)
+{
+
+       if (*p && 0 == *(p + 1))
+               switch (*p) {
+               case('.'):
+                       /* FALLTHROUGH */
+               case(','):
+                       /* FALLTHROUGH */
+               case(';'):
+                       /* FALLTHROUGH */
+               case(':'):
+                       /* FALLTHROUGH */
+               case('?'):
+                       /* FALLTHROUGH */
+               case('!'):
+                       /* FALLTHROUGH */
+               case(')'):
+                       /* FALLTHROUGH */
+               case(']'):
+                       /* FALLTHROUGH */
+               case('}'):
+                       if ( ! (HTML_IGNDELIM & h->flags))
+                               h->flags |= HTML_NOSPACE;
+                       break;
+               default:
+                       break;
+               }
+
+       if ( ! (h->flags & HTML_NOSPACE))
+               printf(" ");
+
+       h->flags &= ~HTML_NOSPACE;
+       h->flags &= ~HTML_NEWLINE;
+
+       if (p)
+               print_encode(h, p);
+
+       if (*p && 0 == *(p + 1))
+               switch (*p) {
+               case('('):
+                       /* FALLTHROUGH */
+               case('['):
+                       /* FALLTHROUGH */
+               case('{'):
+                       h->flags |= HTML_NOSPACE;
+                       break;
+               default:
+                       break;
+               }
+}
+
+
+void
+print_tagq(struct html *h, const struct tag *until)
+{
+       struct tag      *tag;
+
+       while ( ! SLIST_EMPTY(&h->tags)) {
+               tag = SLIST_FIRST(&h->tags);
+               print_ctag(h, tag->tag);
+               SLIST_REMOVE_HEAD(&h->tags, entry);
+               free(tag);
+               if (until && tag == until)
+                       return;
+       }
+}
+
+
+void
+print_stagq(struct html *h, const struct tag *suntil)
+{
+       struct tag      *tag;
+
+       while ( ! SLIST_EMPTY(&h->tags)) {
+               tag = SLIST_FIRST(&h->tags);
+               if (suntil && tag == suntil)
+                       return;
+               print_ctag(h, tag->tag);
+               SLIST_REMOVE_HEAD(&h->tags, entry);
+               free(tag);
+       }
+}
+
+
+void
+bufinit(struct html *h)
+{
+
+       h->buf[0] = '\0';
+       h->buflen = 0;
+}
+
+
+void
+bufcat_style(struct html *h, const char *key, const char *val)
+{
+
+       bufcat(h, key);
+       bufncat(h, ":", 1);
+       bufcat(h, val);
+       bufncat(h, ";", 1);
+}
+
+
+void
+bufcat(struct html *h, const char *p)
+{
+
+       bufncat(h, p, strlen(p));
+}
+
+
+void
+buffmt(struct html *h, const char *fmt, ...)
+{
+       va_list          ap;
+
+       va_start(ap, fmt);
+       (void)vsnprintf(h->buf + (int)h->buflen,
+                       BUFSIZ - h->buflen - 1, fmt, ap);
+       va_end(ap);
+       h->buflen = strlen(h->buf);
+}
+
+
+void
+bufncat(struct html *h, const char *p, size_t sz)
+{
+
+       if (h->buflen + sz > BUFSIZ - 1)
+               sz = BUFSIZ - 1 - h->buflen;
+
+       (void)strncat(h->buf, p, sz);
+       h->buflen += sz;
+}
+
+
+void
+buffmt_includes(struct html *h, const char *name)
+{
+       const char      *p, *pp;
+
+       pp = h->base_includes;
+
+       while (NULL != (p = strchr(pp, '%'))) {
+               bufncat(h, pp, (size_t)(p - pp));
+               switch (*(p + 1)) {
+               case('I'):
+                       bufcat(h, name);
+                       break;
+               default:
+                       bufncat(h, p, 2);
+                       break;
+               }
+               pp = p + 2;
+       }
+       if (pp)
+               bufcat(h, pp);
+}
+
+
+void
+buffmt_man(struct html *h,
+               const char *name, const char *sec)
+{
+       const char      *p, *pp;
+
+       pp = h->base_man;
+
+       /* LINTED */
+       while (NULL != (p = strchr(pp, '%'))) {
+               bufncat(h, pp, (size_t)(p - pp));
+               switch (*(p + 1)) {
+               case('S'):
+                       bufcat(h, sec ? sec : "1");
+                       break;
+               case('N'):
+                       buffmt(h, name);
+                       break;
+               default:
+                       bufncat(h, p, 2);
+                       break;
+               }
+               pp = p + 2;
+       }
+       if (pp)
+               bufcat(h, pp);
+}
+
+
+void
+bufcat_su(struct html *h, const char *p, const struct roffsu *su)
+{
+       double           v;
+       const char      *u;
+
+       v = su->scale;
+
+       switch (su->unit) {
+       case (SCALE_CM):
+               u = "cm";
+               break;
+       case (SCALE_IN):
+               u = "in";
+               break;
+       case (SCALE_PC):
+               u = "pc";
+               break;
+       case (SCALE_PT):
+               u = "pt";
+               break;
+       case (SCALE_EM):
+               u = "em";
+               break;
+       case (SCALE_MM):
+               if (0 == (v /= 100))
+                       v = 1;
+               u = "em";
+               break;
+       case (SCALE_EN):
+               u = "ex";
+               break;
+       case (SCALE_BU):
+               u = "ex";
+               break;
+       case (SCALE_VS):
+               u = "em";
+               break;
+       default:
+               u = "ex";
+               break;
+       }
+
+       if (su->pt)
+               buffmt(h, "%s: %f%s;", p, v, u);
+       else
+               /* LINTED */
+               buffmt(h, "%s: %d%s;", p, (int)v, u);
+}
diff --git a/usr.bin/mandoc/html.h b/usr.bin/mandoc/html.h
new file mode 100644 (file)
index 0000000..758b0be
--- /dev/null
@@ -0,0 +1,133 @@
+/*     $Id: html.h,v 1.1 2009/10/21 19:13:50 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009 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.
+ */
+#ifndef HTML_H
+#define HTML_H
+
+__BEGIN_DECLS
+
+enum   htmltag {
+       TAG_HTML,
+       TAG_HEAD,
+       TAG_BODY,
+       TAG_META,
+       TAG_TITLE,
+       TAG_DIV,
+       TAG_H1,
+       TAG_H2,
+       TAG_P,
+       TAG_SPAN,
+       TAG_LINK,
+       TAG_BR,
+       TAG_A,
+       TAG_TABLE,
+       TAG_COL,
+       TAG_TR,
+       TAG_TD,
+       TAG_LI,
+       TAG_UL,
+       TAG_OL,
+       TAG_BASE,
+       TAG_MAX
+};
+
+enum   htmlattr {
+       ATTR_HTTPEQUIV,
+       ATTR_CONTENT,
+       ATTR_NAME,
+       ATTR_REL,
+       ATTR_HREF,
+       ATTR_TYPE,
+       ATTR_MEDIA,
+       ATTR_CLASS,
+       ATTR_STYLE,
+       ATTR_WIDTH,
+       ATTR_VALIGN,
+       ATTR_TARGET,
+       ATTR_ID,
+       ATTR_MAX
+};
+
+struct tag {
+       enum htmltag      tag;
+       SLIST_ENTRY(tag)  entry;
+};
+
+struct ord {
+       int               pos;
+       const void       *cookie;
+       SLIST_ENTRY(ord)  entry;
+};
+
+SLIST_HEAD(tagq, tag);
+SLIST_HEAD(ordq, ord);
+
+struct htmlpair {
+       enum htmlattr     key;
+       const char       *val;
+};
+
+#define        PAIR_CLASS_INIT(p, v) \
+       do { (p)->key = ATTR_CLASS; \
+       (p)->val = (v); } while (/* CONSTCOND */ 0)
+#define        PAIR_HREF_INIT(p, v) \
+       do { (p)->key = ATTR_HREF; \
+       (p)->val = (v); } while (/* CONSTCOND */ 0)
+#define        PAIR_STYLE_INIT(p, h) \
+       do { (p)->key = ATTR_STYLE; \
+       (p)->val = (h)->buf; } while (/* CONSTCOND */ 0)
+
+struct html {
+       int               flags;
+#define        HTML_NOSPACE     (1 << 0)
+#define        HTML_NEWLINE     (1 << 1)
+#define        HTML_IGNDELIM    (1 << 2)
+       struct tagq       tags;
+       struct ordq       ords;
+       void             *symtab;
+       char             *base;
+       char             *base_man;
+       char             *base_includes;
+       char             *style;
+       char              buf[BUFSIZ];
+       size_t            buflen;
+};
+
+struct roffsu;
+
+void             print_gen_doctype(struct html *);
+void             print_gen_head(struct html *);
+struct tag      *print_otag(struct html *, enum htmltag,
+                               int, const struct htmlpair *);
+void             print_tagq(struct html *, const struct tag *);
+void             print_stagq(struct html *, const struct tag *);
+void             print_text(struct html *, const char *);
+
+void             bufcat_su(struct html *, const char *,
+                       const struct roffsu *);
+void             buffmt_man(struct html *,
+                       const char *, const char *);
+void             buffmt_includes(struct html *, const char *);
+void             buffmt(struct html *, const char *, ...);
+void             bufcat(struct html *, const char *);
+void             bufcat_style(struct html *,
+                       const char *, const char *);
+void             bufncat(struct html *, const char *, size_t);
+void             bufinit(struct html *);
+
+__END_DECLS
+
+#endif /*!HTML_H*/
diff --git a/usr.bin/mandoc/lib.c b/usr.bin/mandoc/lib.c
new file mode 100644 (file)
index 0000000..3cd2ab9
--- /dev/null
@@ -0,0 +1,32 @@
+/*     $Id: lib.c,v 1.2 2009/06/14 23:00:57 schwarze Exp $ */
+/*
+ * Copyright (c) 2009 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 <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 (file)
index 0000000..68a8c8b
--- /dev/null
@@ -0,0 +1,57 @@
+/*     $Id: lib.in,v 1.2 2009/06/14 23:00:57 schwarze Exp $ */
+/*
+ * Copyright (c) 2009 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.
+ */
+
+/*
+ * 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 (file)
index 0000000..f0a5de1
--- /dev/null
@@ -0,0 +1,116 @@
+/*     $Id: libman.h,v 1.9 2009/09/21 21:11:36 schwarze Exp $ */
+/*
+ * Copyright (c) 2009 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.
+ */
+#ifndef LIBMAN_H
+#define LIBMAN_H
+
+#include "man.h"
+
+enum   man_next {
+       MAN_NEXT_SIBLING = 0,
+       MAN_NEXT_CHILD
+};
+
+struct man {
+       void            *data;
+       struct man_cb    cb;
+       int              pflags;
+       int              flags;
+#define        MAN_HALT        (1 << 0)
+#define        MAN_ELINE       (1 << 1)        /* Next-line element scope. */
+#define        MAN_BLINE       (1 << 2)        /* Next-line block scope. */
+#define        MAN_LITERAL     (1 << 3)        /* Literal input. */
+       enum man_next    next;
+       struct man_node *last;
+       struct man_node *first;
+       struct man_meta  meta;
+};
+
+enum   merr {
+       WNPRINT = 0,
+       WNMEM,
+       WMSEC,
+       WDATE,
+       WLNSCOPE,
+       WTSPACE,
+       WTQUOTE,
+       WNODATA,
+       WNOTITLE,
+       WESCAPE,
+       WNUMFMT,
+       WHEADARGS,
+       WBODYARGS,
+       WNHEADARGS,
+       WMACRO,
+       WMACROFORM,
+       WEXITSCOPE,
+       WNOSCOPE,
+       WOLITERAL,
+       WNLITERAL,
+       WERRMAX
+};
+
+#define        MACRO_PROT_ARGS   struct man *m, int tok, int line, \
+                         int ppos, int *pos, char *buf
+
+struct man_macro {
+       int             (*fp)(MACRO_PROT_ARGS);
+       int               flags;
+#define        MAN_SCOPED       (1 << 0)
+#define        MAN_EXPLICIT     (1 << 1)       /* See blk_imp(). */
+#define        MAN_FSCOPED      (1 << 2)       /* See blk_imp(). */
+};
+
+extern const struct man_macro *const man_macros;
+
+__BEGIN_DECLS
+
+#define                  man_perr(m, l, p, t) \
+                 man_err((m), (l), (p), 1, (t))
+#define                  man_pwarn(m, l, p, t) \
+                 man_err((m), (l), (p), 0, (t))
+#define                  man_nerr(m, n, t) \
+                 man_err((m), (n)->line, (n)->pos, 1, (t))
+#define                  man_nwarn(m, n, t) \
+                 man_err((m), (n)->line, (n)->pos, 0, (t))
+
+int              man_word_alloc(struct man *, int, int, const char *);
+int              man_block_alloc(struct man *, int, int, int);
+int              man_head_alloc(struct man *, int, int, int);
+int              man_body_alloc(struct man *, int, int, int);
+int              man_elem_alloc(struct man *, int, int, int);
+void             man_node_free(struct man_node *);
+void             man_node_freelist(struct man_node *);
+void             man_hash_init(void);
+int              man_hash_find(const char *);
+int              man_macroend(struct man *);
+int              man_args(struct man *, int, int *, char *, char **);
+#define        ARGS_ERROR      (-1)
+#define        ARGS_EOLN       (0)
+#define        ARGS_WORD       (1)
+#define        ARGS_QWORD      (1)
+int              man_err(struct man *, int, int, int, enum merr);
+int              man_vwarn(struct man *, int, int, const char *, ...);
+int              man_verr(struct man *, int, int, const char *, ...);
+int              man_valid_post(struct man *);
+int              man_valid_pre(struct man *, const struct man_node *);
+int              man_action_post(struct man *);
+int              man_action_pre(struct man *, struct man_node *);
+int              man_unscope(struct man *, const struct man_node *);
+
+__END_DECLS
+
+#endif /*!LIBMAN_H*/
diff --git a/usr.bin/mandoc/libmandoc.h b/usr.bin/mandoc/libmandoc.h
new file mode 100644 (file)
index 0000000..8441ec3
--- /dev/null
@@ -0,0 +1,26 @@
+/*     $Id: libmandoc.h,v 1.1 2009/07/08 00:04:10 schwarze Exp $ */
+/*
+ * Copyright (c) 2009 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.
+ */
+#ifndef LIBMANDOC_H
+#define LIBMANDOC_H
+
+__BEGIN_DECLS
+
+int             mandoc_special(const char *);
+
+__END_DECLS
+
+#endif /*!LIBMANDOC_H*/
diff --git a/usr.bin/mandoc/libmdoc.h b/usr.bin/mandoc/libmdoc.h
new file mode 100644 (file)
index 0000000..90aded7
--- /dev/null
@@ -0,0 +1,188 @@
+/*     $Id: libmdoc.h,v 1.23 2009/10/21 19:13:50 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009 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.
+ */
+#ifndef LIBMDOC_H
+#define LIBMDOC_H
+
+#include "mdoc.h"
+
+enum   mdoc_next {
+       MDOC_NEXT_SIBLING = 0,
+       MDOC_NEXT_CHILD
+};
+
+struct mdoc {
+       void             *data;
+       struct mdoc_cb    cb;
+       int               flags;
+#define        MDOC_HALT        (1 << 0)       /* Error in parse. Halt. */
+#define        MDOC_LITERAL     (1 << 1)       /* In a literal scope. */
+#define        MDOC_PBODY       (1 << 2)       /* In the document body. */
+       int               pflags;
+       enum mdoc_next    next;
+       struct mdoc_node *last;
+       struct mdoc_node *first;
+       struct mdoc_meta  meta;
+       enum mdoc_sec     lastnamed;
+       enum mdoc_sec     lastsec;
+};
+
+enum   merr {
+       ETAILWS = 0,
+       EQUOTPARM,
+       EQUOTTERM,
+       EMALLOC,
+       EARGVAL,
+       EBODYPROL,
+       EPROLBODY,
+       ETEXTPROL,
+       ENOBLANK,
+       ETOOLONG,
+       EESCAPE,
+       EPRINT,
+       ENODAT,
+       ENOPROLOGUE,
+       ELINE,
+       EATT,
+       ENAME,
+       ELISTTYPE,
+       EDISPTYPE,
+       EMULTIDISP,
+       EMULTILIST,
+       ESECNAME,
+       ENAMESECINC,
+       EARGREP,
+       EBOOL,
+       ECOLMIS,
+       ENESTDISP,
+       EMISSWIDTH,
+       EWRONGMSEC,
+       ESECOOO,
+       ESECREP,
+       EBADSTAND,
+       ENOMULTILINE,
+       EMULTILINE,
+       ENOLINE,
+       EPROLOOO,
+       EPROLREP,
+       EBADMSEC,
+       EBADSEC,
+       EFONT,
+       EBADDATE,
+       ENUMFMT,
+       ENOWIDTH,
+       EUTSNAME,
+       EOBS,
+       EIMPBRK,
+       EIGNE,
+       EOPEN,
+       EQUOTPHR,
+       ENOCTX,
+       ELIB,
+       EBADCHILD,
+       ENOTYPE,
+       MERRMAX
+};
+
+#define        MACRO_PROT_ARGS struct mdoc *m, int tok, int line, \
+                       int ppos, int *pos, char *buf
+
+struct mdoc_macro {
+       int             (*fp)(MACRO_PROT_ARGS);
+       int               flags;
+#define        MDOC_CALLABLE    (1 << 0)
+#define        MDOC_PARSED      (1 << 1)
+#define        MDOC_EXPLICIT    (1 << 2)
+#define        MDOC_PROLOGUE    (1 << 3)
+#define        MDOC_IGNDELIM    (1 << 4)
+       /* Reserved words in arguments treated as text. */
+};
+
+extern const struct mdoc_macro *const mdoc_macros;
+
+__BEGIN_DECLS
+
+#define                  mdoc_perr(m, l, p, t) \
+                 mdoc_err((m), (l), (p), 1, (t))
+#define                  mdoc_pwarn(m, l, p, t) \
+                 mdoc_err((m), (l), (p), 0, (t))
+#define                  mdoc_nerr(m, n, t) \
+                 mdoc_err((m), (n)->line, (n)->pos, 1, (t))
+#define                  mdoc_nwarn(m, n, t) \
+                 mdoc_err((m), (n)->line, (n)->pos, 0, (t))
+
+int              mdoc_err(struct mdoc *, int, int, int, enum merr);
+int              mdoc_verr(struct mdoc *, int, int, const char *, ...);
+int              mdoc_vwarn(struct mdoc *, int, int, const char *, ...);
+
+int              mdoc_macro(MACRO_PROT_ARGS);
+int              mdoc_word_alloc(struct mdoc *,
+                       int, int, const char *);
+int              mdoc_elem_alloc(struct mdoc *, int, int,
+                       int, struct mdoc_arg *);
+int              mdoc_block_alloc(struct mdoc *, int, int,
+                       int, struct mdoc_arg *);
+int              mdoc_head_alloc(struct mdoc *, int, int, int);
+int              mdoc_tail_alloc(struct mdoc *, int, int, int);
+int              mdoc_body_alloc(struct mdoc *, int, int, int);
+void             mdoc_node_free(struct mdoc_node *);
+void             mdoc_node_freelist(struct mdoc_node *);
+void             mdoc_hash_init(void);
+int              mdoc_hash_find(const char *);
+int              mdoc_iscdelim(char);
+int              mdoc_isdelim(const char *);
+size_t           mdoc_isescape(const char *);
+enum   mdoc_sec  mdoc_atosec(const char *);
+time_t           mdoc_atotime(const char *);
+
+size_t           mdoc_macro2len(int);
+const char      *mdoc_a2att(const char *);
+const char      *mdoc_a2lib(const char *);
+const char      *mdoc_a2st(const char *);
+const char      *mdoc_a2arch(const char *);
+const char      *mdoc_a2vol(const char *);
+const char      *mdoc_a2msec(const char *);
+int              mdoc_valid_pre(struct mdoc *,
+                       const struct mdoc_node *);
+int              mdoc_valid_post(struct mdoc *);
+int              mdoc_action_pre(struct mdoc *,
+                       const struct mdoc_node *);
+int              mdoc_action_post(struct mdoc *);
+int              mdoc_argv(struct mdoc *, int, int,
+                       struct mdoc_arg **, int *, char *);
+#define        ARGV_ERROR      (-1)
+#define        ARGV_EOLN       (0)
+#define        ARGV_ARG        (1)
+#define        ARGV_WORD       (2)
+void             mdoc_argv_free(struct mdoc_arg *);
+int              mdoc_args(struct mdoc *, int,
+                       int *, char *, int, char **);
+int              mdoc_zargs(struct mdoc *, int,
+                       int *, char *, int, char **);
+#define        ARGS_DELIM      (1 << 1)        /* See args(). */
+#define        ARGS_TABSEP     (1 << 2)        /* See args(). */
+#define        ARGS_NOWARN     (1 << 3)        /* See args(). */
+#define        ARGS_ERROR      (-1)
+#define        ARGS_EOLN       (0)
+#define        ARGS_WORD       (1)
+#define        ARGS_PUNCT      (2)
+#define        ARGS_QWORD      (3)
+#define        ARGS_PHRASE     (4)
+int              mdoc_macroend(struct mdoc *);
+
+__END_DECLS
+
+#endif /*!LIBMDOC_H*/
diff --git a/usr.bin/mandoc/main.c b/usr.bin/mandoc/main.c
new file mode 100644 (file)
index 0000000..03b8f2c
--- /dev/null
@@ -0,0 +1,662 @@
+/*     $Id: main.c,v 1.17 2009/10/21 19:13:50 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009 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/stat.h>
+
+#include <assert.h>
+#include <err.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mdoc.h"
+#include "man.h"
+#include "main.h"
+
+#define        UNCONST(a)      ((void *)(uintptr_t)(const void *)(a))
+
+typedef        void            (*out_mdoc)(void *, const struct mdoc *);
+typedef        void            (*out_man)(void *, const struct man *);
+typedef        void            (*out_free)(void *);
+
+struct buf {
+       char             *buf;
+       size_t            sz;
+};
+
+enum   intt {
+       INTT_AUTO,
+       INTT_MDOC,
+       INTT_MAN
+};
+
+enum   outt {
+       OUTT_ASCII = 0,
+       OUTT_TREE,
+       OUTT_HTML,
+       OUTT_LINT
+};
+
+struct curparse {
+       const char       *file;         /* Current parse. */
+       int               fd;           /* Current parse. */
+       int               wflags;
+#define        WARN_WALL        (1 << 0)       /* All-warnings mask. */
+#define        WARN_WERR        (1 << 2)       /* Warnings->errors. */
+       int               fflags;
+#define        IGN_SCOPE        (1 << 0)       /* Ignore scope errors. */
+#define        NO_IGN_ESCAPE    (1 << 1)       /* Don't ignore bad escapes. */
+#define        NO_IGN_MACRO     (1 << 2)       /* Don't ignore bad macros. */
+#define        NO_IGN_CHARS     (1 << 3)       /* Don't ignore bad chars. */
+#define        IGN_ERRORS       (1 << 4)       /* Ignore failed parse. */
+       enum intt         inttype;      /* Input parsers... */
+       struct man       *man;
+       struct man       *lastman;
+       struct mdoc      *mdoc;
+       struct mdoc      *lastmdoc;
+       enum outt         outtype;      /* Output devices... */
+       out_mdoc          outmdoc;
+       out_man           outman;
+       out_free          outfree;
+       void             *outdata;
+       char             *outopts;
+};
+
+static int               foptions(int *, char *);
+static int               toptions(enum outt *, char *);
+static int               moptions(enum intt *, char *);
+static int               woptions(int *, char *);
+static int               merr(void *, int, int, const char *);
+static int               mwarn(void *, int, int, const char *);
+static int               ffile(struct buf *, struct buf *,
+                               const char *, struct curparse *);
+static int               fdesc(struct buf *, struct buf *,
+                               struct curparse *);
+static int               pset(const char *, int, struct curparse *,
+                               struct man **, struct mdoc **);
+static struct man       *man_init(struct curparse *);
+static struct mdoc      *mdoc_init(struct curparse *);
+__dead2        static void       version(void);
+__dead2        static void       usage(void);
+
+extern char             *__progname;
+
+
+int
+main(int argc, char *argv[])
+{
+       int              c, rc;
+       struct buf       ln, blk;
+       struct curparse  curp;
+
+       bzero(&curp, sizeof(struct curparse));
+
+       curp.inttype = INTT_AUTO;
+       curp.outtype = OUTT_ASCII;
+
+       /* LINTED */
+       while (-1 != (c = getopt(argc, argv, "f:m:o:T:VW:")))
+               switch (c) {
+               case ('f'):
+                       if ( ! foptions(&curp.fflags, optarg))
+                               return(EXIT_FAILURE);
+                       break;
+               case ('m'):
+                       if ( ! moptions(&curp.inttype, optarg))
+                               return(EXIT_FAILURE);
+                       break;
+               case ('o'):
+                       curp.outopts = optarg;
+                       break;
+               case ('T'):
+                       if ( ! toptions(&curp.outtype, optarg))
+                               return(EXIT_FAILURE);
+                       break;
+               case ('W'):
+                       if ( ! woptions(&curp.wflags, optarg))
+                               return(EXIT_FAILURE);
+                       break;
+               case ('V'):
+                       version();
+                       /* NOTREACHED */
+               default:
+                       usage();
+                       /* NOTREACHED */
+               }
+
+       argc -= optind;
+       argv += optind;
+
+       bzero(&ln, sizeof(struct buf));
+       bzero(&blk, sizeof(struct buf));
+
+       rc = 1;
+
+       if (NULL == *argv) {
+               curp.file = "<stdin>";
+               curp.fd = STDIN_FILENO;
+
+               c = fdesc(&blk, &ln, &curp);
+               if ( ! (IGN_ERRORS & curp.fflags))
+                       rc = 1 == c ? 1 : 0;
+               else
+                       rc = -1 == c ? 0 : 1;
+       }
+
+       while (rc && *argv) {
+               c = ffile(&blk, &ln, *argv, &curp);
+               if ( ! (IGN_ERRORS & curp.fflags))
+                       rc = 1 == c ? 1 : 0;
+               else
+                       rc = -1 == c ? 0 : 1;
+
+               argv++;
+               if (*argv && rc) {
+                       if (curp.lastman)
+                               if ( ! man_reset(curp.lastman))
+                                       rc = 0;
+                       if (curp.lastmdoc)
+                               if ( ! mdoc_reset(curp.lastmdoc))
+                                       rc = 0;
+                       curp.lastman = NULL;
+                       curp.lastmdoc = NULL;
+               }
+       }
+
+       if (blk.buf)
+               free(blk.buf);
+       if (ln.buf)
+               free(ln.buf);
+       if (curp.outfree)
+               (*curp.outfree)(curp.outdata);
+       if (curp.mdoc)
+               mdoc_free(curp.mdoc);
+       if (curp.man)
+               man_free(curp.man);
+
+       return(rc ? EXIT_SUCCESS : EXIT_FAILURE);
+}
+
+
+__dead2 static void
+version(void)
+{
+
+       (void)printf("%s %s\n", __progname, VERSION);
+       exit(EXIT_SUCCESS);
+}
+
+
+__dead2 static void
+usage(void)
+{
+
+       (void)fprintf(stderr, "usage: %s [-V] [-foption...] "
+                       "[-mformat] [-Toutput] [-Werr...]\n",
+                       __progname);
+       exit(EXIT_FAILURE);
+}
+
+
+static struct man *
+man_init(struct curparse *curp)
+{
+       int              pflags;
+       struct man      *man;
+       struct man_cb    mancb;
+
+       mancb.man_err = merr;
+       mancb.man_warn = mwarn;
+
+       /* Defaults from mandoc.1. */
+
+       pflags = MAN_IGN_MACRO | MAN_IGN_ESCAPE | MAN_IGN_CHARS;
+
+       if (curp->fflags & NO_IGN_MACRO)
+               pflags &= ~MAN_IGN_MACRO;
+       if (curp->fflags & NO_IGN_CHARS)
+               pflags &= ~MAN_IGN_CHARS;
+       if (curp->fflags & NO_IGN_ESCAPE)
+               pflags &= ~MAN_IGN_ESCAPE;
+
+       if (NULL == (man = man_alloc(curp, pflags, &mancb)))
+               warnx("memory exhausted");
+
+       return(man);
+}
+
+
+static struct mdoc *
+mdoc_init(struct curparse *curp)
+{
+       int              pflags;
+       struct mdoc     *mdoc;
+       struct mdoc_cb   mdoccb;
+
+       mdoccb.mdoc_err = merr;
+       mdoccb.mdoc_warn = mwarn;
+
+       /* Defaults from mandoc.1. */
+
+       pflags = MDOC_IGN_MACRO | MDOC_IGN_ESCAPE | MDOC_IGN_CHARS;
+
+       if (curp->fflags & IGN_SCOPE)
+               pflags |= MDOC_IGN_SCOPE;
+       if (curp->fflags & NO_IGN_ESCAPE)
+               pflags &= ~MDOC_IGN_ESCAPE;
+       if (curp->fflags & NO_IGN_MACRO)
+               pflags &= ~MDOC_IGN_MACRO;
+       if (curp->fflags & NO_IGN_CHARS)
+               pflags &= ~MDOC_IGN_CHARS;
+
+       if (NULL == (mdoc = mdoc_alloc(curp, pflags, &mdoccb)))
+               warnx("memory exhausted");
+
+       return(mdoc);
+}
+
+
+static int
+ffile(struct buf *blk, struct buf *ln,
+               const char *file, struct curparse *curp)
+{
+       int              c;
+
+       curp->file = file;
+       if (-1 == (curp->fd = open(curp->file, O_RDONLY, 0))) {
+               warn("%s", curp->file);
+               return(-1);
+       }
+
+       c = fdesc(blk, ln, curp);
+
+       if (-1 == close(curp->fd))
+               warn("%s", curp->file);
+
+       return(c);
+}
+
+
+static int
+fdesc(struct buf *blk, struct buf *ln, struct curparse *curp)
+{
+       size_t           sz;
+       ssize_t          ssz;
+       struct stat      st;
+       int              j, i, pos, lnn, comment;
+       struct man      *man;
+       struct mdoc     *mdoc;
+
+       sz = BUFSIZ;
+       man = NULL;
+       mdoc = NULL;
+
+       /*
+        * Two buffers: ln and buf.  buf is the input buffer optimised
+        * here for each file's block size.  ln is a line buffer.  Both
+        * growable, hence passed in by ptr-ptr.
+        */
+
+       if (-1 == fstat(curp->fd, &st))
+               warn("%s", curp->file);
+       else if ((size_t)st.st_blksize > sz)
+               sz = st.st_blksize;
+
+       if (sz > blk->sz) {
+               blk->buf = realloc(blk->buf, sz);
+               if (NULL == blk->buf) {
+                       warn("realloc");
+                       return(-1);
+               }
+               blk->sz = sz;
+       }
+
+       /* Fill buf with file blocksize. */
+
+       for (lnn = pos = comment = 0; ; ) {
+               if (-1 == (ssz = read(curp->fd, blk->buf, sz))) {
+                       warn("%s", curp->file);
+                       return(-1);
+               } else if (0 == ssz)
+                       break;
+
+               /* Parse the read block into partial or full lines. */
+
+               for (i = 0; i < (int)ssz; i++) {
+                       if (pos >= (int)ln->sz) {
+                               ln->sz += 256; /* Step-size. */
+                               ln->buf = realloc(ln->buf, ln->sz);
+                               if (NULL == ln->buf) {
+                                       warn("realloc");
+                                       return(-1);
+                               }
+                       }
+
+                       if ('\n' != blk->buf[i]) {
+                               if (comment)
+                                       continue;
+                               ln->buf[pos++] = blk->buf[i];
+
+                               /* Handle in-line `\"' comments. */
+
+                               if (1 == pos || '\"' != ln->buf[pos - 1])
+                                       continue;
+
+                               for (j = pos - 2; j >= 0; j--)
+                                       if ('\\' != ln->buf[j])
+                                               break;
+
+                               if ( ! ((pos - 2 - j) % 2))
+                                       continue;
+
+                               comment = 1;
+                               pos -= 2;
+                               continue;
+                       }
+
+                       /* Handle escaped `\\n' newlines. */
+
+                       if (pos > 0 && 0 == comment &&
+                                       '\\' == ln->buf[pos - 1]) {
+                               for (j = pos - 1; j >= 0; j--)
+                                       if ('\\' != ln->buf[j])
+                                               break;
+                               if ( ! ((pos - j) % 2)) {
+                                       pos--;
+                                       lnn++;
+                                       continue;
+                               }
+                       }
+
+                       ln->buf[pos] = 0;
+                       lnn++;
+
+                       /* If unset, assign parser in pset(). */
+
+                       if ( ! (man || mdoc) && ! pset(ln->buf,
+                                               pos, curp, &man, &mdoc))
+                               return(-1);
+
+                       pos = comment = 0;
+
+                       /* Pass down into parsers. */
+
+                       if (man && ! man_parseln(man, lnn, ln->buf))
+                               return(0);
+                       if (mdoc && ! mdoc_parseln(mdoc, lnn, ln->buf))
+                               return(0);
+               }
+       }
+
+       /* NOTE a parser may not have been assigned, yet. */
+
+       if ( ! (man || mdoc)) {
+               (void)fprintf(stderr, "%s: not a manual\n",
+                               curp->file);
+               return(0);
+       }
+
+       if (mdoc && ! mdoc_endparse(mdoc))
+               return(0);
+       if (man && ! man_endparse(man))
+               return(0);
+
+       /* If unset, allocate output dev now (if applicable). */
+
+       if ( ! (curp->outman && curp->outmdoc)) {
+               switch (curp->outtype) {
+               case (OUTT_HTML):
+                       curp->outdata = html_alloc(curp->outopts);
+                       curp->outman = html_man;
+                       curp->outmdoc = html_mdoc;
+                       curp->outfree = html_free;
+                       break;
+               case (OUTT_TREE):
+                       curp->outman = tree_man;
+                       curp->outmdoc = tree_mdoc;
+                       break;
+               case (OUTT_LINT):
+                       break;
+               default:
+                       curp->outdata = ascii_alloc();
+                       curp->outman = terminal_man;
+                       curp->outmdoc = terminal_mdoc;
+                       curp->outfree = terminal_free;
+                       break;
+               }
+       }
+
+       /* Execute the out device, if it exists. */
+
+       if (man && curp->outman)
+               (*curp->outman)(curp->outdata, man);
+       if (mdoc && curp->outmdoc)
+               (*curp->outmdoc)(curp->outdata, mdoc);
+
+       return(1);
+}
+
+
+static int
+pset(const char *buf, int pos, struct curparse *curp,
+               struct man **man, struct mdoc **mdoc)
+{
+       int              i;
+
+       /*
+        * Try to intuit which kind of manual parser should be used.  If
+        * passed in by command-line (-man, -mdoc), then use that
+        * explicitly.  If passed as -mandoc, then try to guess from the
+        * line: either skip dot-lines, use -mdoc when finding `.Dt', or
+        * default to -man, which is more lenient.
+        */
+
+       if (buf[0] == '.') {
+               for (i = 1; buf[i]; i++)
+                       if (' ' != buf[i] && '\t' != buf[i])
+                               break;
+               if (0 == buf[i])
+                       return(1);
+       }
+
+       switch (curp->inttype) {
+       case (INTT_MDOC):
+               if (NULL == curp->mdoc)
+                       curp->mdoc = mdoc_init(curp);
+               if (NULL == (*mdoc = curp->mdoc))
+                       return(0);
+               curp->lastmdoc = *mdoc;
+               return(1);
+       case (INTT_MAN):
+               if (NULL == curp->man)
+                       curp->man = man_init(curp);
+               if (NULL == (*man = curp->man))
+                       return(0);
+               curp->lastman = *man;
+               return(1);
+       default:
+               break;
+       }
+
+       if (pos >= 3 && 0 == memcmp(buf, ".Dd", 3))  {
+               if (NULL == curp->mdoc)
+                       curp->mdoc = mdoc_init(curp);
+               if (NULL == (*mdoc = curp->mdoc))
+                       return(0);
+               curp->lastmdoc = *mdoc;
+               return(1);
+       }
+
+       if (NULL == curp->man)
+               curp->man = man_init(curp);
+       if (NULL == (*man = curp->man))
+               return(0);
+       curp->lastman = *man;
+       return(1);
+}
+
+
+static int
+moptions(enum intt *tflags, char *arg)
+{
+
+       if (0 == strcmp(arg, "doc"))
+               *tflags = INTT_MDOC;
+       else if (0 == strcmp(arg, "andoc"))
+               *tflags = INTT_AUTO;
+       else if (0 == strcmp(arg, "an"))
+               *tflags = INTT_MAN;
+       else {
+               warnx("bad argument: -m%s", arg);
+               return(0);
+       }
+
+       return(1);
+}
+
+
+static int
+toptions(enum outt *tflags, char *arg)
+{
+
+       if (0 == strcmp(arg, "ascii"))
+               *tflags = OUTT_ASCII;
+       else if (0 == strcmp(arg, "lint"))
+               *tflags = OUTT_LINT;
+       else if (0 == strcmp(arg, "tree"))
+               *tflags = OUTT_TREE;
+       else if (0 == strcmp(arg, "html"))
+               *tflags = OUTT_HTML;
+       else {
+               warnx("bad argument: -T%s", arg);
+               return(0);
+       }
+
+       return(1);
+}
+
+
+static int
+foptions(int *fflags, char *arg)
+{
+       char            *v, *o;
+       const char      *toks[7];
+
+       toks[0] = "ign-scope";
+       toks[1] = "no-ign-escape";
+       toks[2] = "no-ign-macro";
+       toks[3] = "no-ign-chars";
+       toks[4] = "ign-errors";
+       toks[5] = "strict";
+       toks[6] = NULL;
+
+       while (*arg) {
+               o = arg;
+               switch (getsubopt(&arg, UNCONST(toks), &v)) {
+               case (0):
+                       *fflags |= IGN_SCOPE;
+                       break;
+               case (1):
+                       *fflags |= NO_IGN_ESCAPE;
+                       break;
+               case (2):
+                       *fflags |= NO_IGN_MACRO;
+                       break;
+               case (3):
+                       *fflags |= NO_IGN_CHARS;
+                       break;
+               case (4):
+                       *fflags |= IGN_ERRORS;
+                       break;
+               case (5):
+                       *fflags |= NO_IGN_ESCAPE |
+                                  NO_IGN_MACRO | NO_IGN_CHARS;
+                       break;
+               default:
+                       warnx("bad argument: -f%s", o);
+                       return(0);
+               }
+       }
+
+       return(1);
+}
+
+
+static int
+woptions(int *wflags, char *arg)
+{
+       char            *v, *o;
+       const char      *toks[3];
+
+       toks[0] = "all";
+       toks[1] = "error";
+       toks[2] = NULL;
+
+       while (*arg) {
+               o = arg;
+               switch (getsubopt(&arg, UNCONST(toks), &v)) {
+               case (0):
+                       *wflags |= WARN_WALL;
+                       break;
+               case (1):
+                       *wflags |= WARN_WERR;
+                       break;
+               default:
+                       warnx("bad argument: -W%s", o);
+                       return(0);
+               }
+       }
+
+       return(1);
+}
+
+
+/* ARGSUSED */
+static int
+merr(void *arg, int line, int col, const char *msg)
+{
+       struct curparse *curp;
+
+       curp = (struct curparse *)arg;
+
+       (void)fprintf(stderr, "%s:%d:%d: error: %s\n",
+                       curp->file, line, col + 1, msg);
+
+       return(0);
+}
+
+
+static int
+mwarn(void *arg, int line, int col, const char *msg)
+{
+       struct curparse *curp;
+
+       curp = (struct curparse *)arg;
+
+       if ( ! (curp->wflags & WARN_WALL))
+               return(1);
+
+       (void)fprintf(stderr, "%s:%d:%d: warning: %s\n",
+                       curp->file, line, col + 1, msg);
+
+       if ( ! (curp->wflags & WARN_WERR))
+               return(1);
+
+       return(0);
+}
diff --git a/usr.bin/mandoc/main.h b/usr.bin/mandoc/main.h
new file mode 100644 (file)
index 0000000..8eaa82c
--- /dev/null
@@ -0,0 +1,47 @@
+/*     $Id: main.h,v 1.1 2009/10/21 19:13:50 schwarze Exp $ */
+/*
+ * Copyright (c) 2009 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.
+ */
+#ifndef        MAIN_H
+#define        MAIN_H
+
+__BEGIN_DECLS
+
+struct mdoc;
+struct man;
+
+/*
+ * Definitions for main.c-visible output device functions, e.g., -Thtml
+ * and -Tascii.  Note that ascii_alloc() is named as such in
+ * anticipation of latin1_alloc() and so on, all of which map into the
+ * terminal output routines with different character settings.
+ */
+
+void            *html_alloc(char *);
+void             html_mdoc(void *, const struct mdoc *);
+void             html_man(void *, const struct man *);
+void             html_free(void *);
+
+void             tree_mdoc(void *, const struct mdoc *);
+void             tree_man(void *, const struct man *);
+
+void            *ascii_alloc(void);
+void             terminal_mdoc(void *, const struct mdoc *);
+void             terminal_man(void *, const struct man *);
+void             terminal_free(void *);
+
+__END_DECLS
+
+#endif /*!MAIN_H*/
diff --git a/usr.bin/mandoc/man.3 b/usr.bin/mandoc/man.3
new file mode 100644 (file)
index 0000000..acb2874
--- /dev/null
@@ -0,0 +1,283 @@
+.\"    $Id: man.3,v 1.6 2009/10/20 10:15:04 schwarze Exp $
+.\"
+.\" Copyright (c) 2009 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.
+.\"
+.Dd $Mdocdate: August 22 2009 $
+.Dt MAN 3
+.Os
+.\" SECTION
+.Sh NAME
+.Nm man_alloc ,
+.Nm man_parseln ,
+.Nm man_endparse ,
+.Nm man_node ,
+.Nm man_meta ,
+.Nm man_free ,
+.Nm man_reset
+.Nd man macro compiler library
+.\" SECTION
+.Sh SYNOPSIS
+.In man.h
+.Vt extern const char * const * man_macronames;
+.Ft "struct man *"
+.Fn man_alloc "void *data" "int pflags" "const struct man_cb *cb"
+.Ft void
+.Fn man_reset "struct man *man"
+.Ft void
+.Fn man_free "struct man *man"
+.Ft int
+.Fn man_parseln "struct man *man" "int line" "char *buf"
+.Ft "const struct man_node *"
+.Fn man_node "const struct man *man"
+.Ft "const struct man_meta *"
+.Fn man_meta "const struct man *man"
+.Ft int
+.Fn man_endparse "struct man *man"
+.\" SECTION
+.Sh DESCRIPTION
+The
+.Nm man
+library parses lines of
+.Xr man 7
+input (and
+.Em only
+man) into an abstract syntax tree (AST).
+.\" PARAGRAPH
+.Pp
+In general, applications initiate a parsing sequence with
+.Fn man_alloc ,
+parse each line in a document with
+.Fn man_parseln ,
+close the parsing session with
+.Fn man_endparse ,
+operate over the syntax tree returned by
+.Fn man_node
+and
+.Fn man_meta ,
+then free all allocated memory with
+.Fn man_free .
+The
+.Fn man_reset
+function may be used in order to reset the parser for another input
+sequence.  See the
+.Sx EXAMPLES
+section for a full example.
+.\" PARAGRAPH
+.Pp
+This section further defines the
+.Sx Types ,
+.Sx Functions
+and
+.Sx Variables
+available to programmers.  Following that, the
+.Sx Abstract Syntax Tree
+section documents the output tree.
+.\" SUBSECTION
+.Ss Types
+Both functions (see
+.Sx Functions )
+and variables (see
+.Sx Variables )
+may use the following types:
+.Bl -ohang -offset "XXXX"
+.\" LIST-ITEM
+.It Vt struct man
+An opaque type defined in
+.Pa man.c .
+Its values are only used privately within the library.
+.\" LIST-ITEM
+.It Vt struct man_cb
+A set of message callbacks defined in
+.Pa man.h .
+.\" LIST-ITEM
+.It Vt struct man_node
+A parsed node.  Defined in
+.Pa man.h .
+See
+.Sx Abstract Syntax Tree
+for details.
+.El
+.\" SUBSECTION
+.Ss Functions
+Function descriptions follow:
+.Bl -ohang -offset "XXXX"
+.\" LIST-ITEM
+.It Fn man_alloc
+Allocates a parsing structure.  The
+.Fa data
+pointer is passed to callbacks in
+.Fa cb ,
+which are documented further in the header file.
+The
+.Fa pflags
+arguments are defined in
+.Pa man.h .
+Returns NULL on failure.  If non-NULL, the pointer must be freed with
+.Fn man_free .
+.\" LIST-ITEM
+.It Fn man_reset
+Reset the parser for another parse routine.  After its use,
+.Fn man_parseln
+behaves as if invoked for the first time.
+.\" LIST-ITEM
+.It Fn man_free
+Free all resources of a parser.  The pointer is no longer valid after
+invocation.
+.\" LIST-ITEM
+.It Fn man_parseln
+Parse a nil-terminated line of input.  This line should not contain the
+trailing newline.  Returns 0 on failure, 1 on success.  The input buffer
+.Fa buf
+is modified by this function.
+.\" LIST-ITEM
+.It Fn man_endparse
+Signals that the parse is complete.  Note that if
+.Fn man_endparse
+is called subsequent to
+.Fn man_node ,
+the resulting tree is incomplete.  Returns 0 on failure, 1 on success.
+.\" LIST-ITEM
+.It Fn man_node
+Returns the first node of the parse.  Note that if
+.Fn man_parseln
+or
+.Fn man_endparse
+return 0, the tree will be incomplete.
+.It Fn man_meta
+Returns the document's parsed meta-data.  If this information has not
+yet been supplied or
+.Fn man_parseln
+or
+.Fn man_endparse
+return 0, the data will be incomplete.
+.El
+.\" SUBSECTION
+.Ss Variables
+The following variables are also defined:
+.Bl -ohang -offset "XXXX"
+.\" LIST-ITEM
+.It Va man_macronames
+An array of string-ified token names.
+.El
+.\" SUBSECTION
+.Ss Abstract Syntax Tree
+The
+.Nm
+functions produce an abstract syntax tree (AST) describing input in a
+regular form.  It may be reviewed at any time with
+.Fn man_nodes ;
+however, if called before
+.Fn man_endparse ,
+or after
+.Fn man_endparse
+or
+.Fn man_parseln
+fail, it may be incomplete.
+.\" PARAGRAPH
+.Pp
+This AST is governed by the ontological
+rules dictated in
+.Xr man 7
+and derives its terminology accordingly.
+.\" PARAGRAPH
+.Pp
+The AST is composed of
+.Vt struct man_node
+nodes with element, root and text types as declared
+by the
+.Va type
+field.  Each node also provides its parse point (the
+.Va line ,
+.Va sec ,
+and
+.Va pos
+fields), its position in the tree (the
+.Va parent ,
+.Va child ,
+.Va next
+and
+.Va prev
+fields) and some type-specific data.
+.\" PARAGRAPH
+.Pp
+The tree itself is arranged according to the following normal form,
+where capitalised non-terminals represent nodes.
+.Pp
+.Bl -tag -width "ELEMENTXX" -compact -offset "XXXX"
+.\" LIST-ITEM
+.It ROOT
+\(<- mnode+
+.It mnode
+\(<- ELEMENT | TEXT | BLOCK
+.It BLOCK
+\(<- HEAD BODY
+.It HEAD
+\(<- mnode*
+.It BODY
+\(<- mnode*
+.It ELEMENT
+\(<- ELEMENT | TEXT*
+.It TEXT
+\(<- [[:alpha:]]*
+.El
+.\" PARAGRAPH
+.Pp
+The only elements capable of nesting other elements are those with
+next-lint scope as documented in
+.Xr man 7 .
+.\" SECTION
+.Sh EXAMPLES
+The following example reads lines from stdin and parses them, operating
+on the finished parse tree with
+.Fn parsed .
+Note that, if the last line of the file isn't newline-terminated, this
+will truncate the file's last character (see
+.Xr fgetln 3 ) .
+Further, this example does not error-check nor free memory upon failure.
+.Bd -literal -offset "XXXX"
+struct man *man;
+struct man_node *node;
+char *buf;
+size_t len;
+int line;
+
+line = 1;
+man = man_alloc(NULL, 0, NULL);
+
+while ((buf = fgetln(fp, &len))) {
+       buf[len - 1] = '\\0';
+       if ( ! man_parseln(man, line, buf))
+               errx(1, "man_parseln");
+       line++;
+}
+
+if ( ! man_endparse(man))
+       errx(1, "man_endparse");
+if (NULL == (node = man_node(man)))
+       errx(1, "man_node");
+
+parsed(man, node);
+man_free(man);
+.Ed
+.\" SECTION
+.Sh SEE ALSO
+.Xr mandoc 1 ,
+.Xr man 7
+.\" SECTION
+.Sh AUTHORS
+The
+.Nm
+utility was written by
+.An Kristaps Dzonsons Aq kristaps@kth.se .
diff --git a/usr.bin/mandoc/man.7 b/usr.bin/mandoc/man.7
new file mode 100644 (file)
index 0000000..974eda2
--- /dev/null
@@ -0,0 +1,589 @@
+.\"    $Id: man.7,v 1.12 2009/10/21 19:13:50 schwarze Exp $
+.\"
+.\" Copyright (c) 2009 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.
+.\"
+.Dd $Mdocdate: September 18 2009 $
+.Dt MAN 7
+.Os
+.
+.
+.Sh NAME
+.Nm man
+.Nd man language reference
+.
+.
+.Sh DESCRIPTION
+The
+.Nm man
+language was historically used to format
+.Ux
+manuals.  This reference document describes its syntax, structure, and
+usage.
+.
+.Pp
+.Bf -emphasis
+Do not use
+.Nm
+to write your manuals.
+.Ef
+Use the
+.Xr mdoc 7
+language, instead.
+.
+.Pp
+An
+.Nm
+document follows simple rules:  lines beginning with the control
+character
+.Sq \&.
+are parsed for macros.  Other lines are interpreted within the scope of
+prior macros:
+.Bd -literal -offset indent
+\&.SH Macro lines change control state.
+Other lines are interpreted within the current state.
+.Ed
+.
+.
+.Sh INPUT ENCODING
+.Nm
+documents may contain only graphable 7-bit ASCII characters, the
+space character, and the tabs character.  All manuals must have
+.Ux
+line termination.
+.
+.Pp
+Blank lines are acceptable; where found, the output will assert a
+vertical space.
+.
+.Pp
+The
+.Sq \ec
+escape is common in historical
+.Nm
+documents; if encountered at the end of a word, it ensures that the
+subsequent word isn't off-set by whitespace.
+.
+.
+.Ss Comments
+Text following a
+.Sq \e\*" ,
+whether in a macro or free-form text line, is ignored to the end of
+line.  A macro line with only a control character and comment escape,
+.Sq \&.\e" ,
+is also ignored.  Macro lines with only a control charater and
+optionally whitespace are stripped from input.
+.
+.
+.Ss Special Characters
+Special characters may occur in both macro and free-form lines.
+Sequences begin with the escape character
+.Sq \e
+followed by either an open-parenthesis
+.Sq \&(
+for two-character sequences; an open-bracket
+.Sq \&[
+for n-character sequences (terminated at a close-bracket
+.Sq \&] ) ;
+or a single one-character sequence.  See
+.Xr mandoc_char 7
+for a complete list.  Examples include
+.Sq \e(em
+.Pq em-dash
+and
+.Sq \ee
+.Pq back-slash .
+.
+.
+.Ss Text Decoration
+Terms may be text-decorated using the
+.Sq \ef
+escape followed by an indicator: B (bold), I, (italic), or P and R
+(Roman, or reset).
+.
+.
+.Ss Whitespace
+Unless specifically escaped, consecutive blocks of whitespace are pruned
+from input.  These are later re-added, if applicable, by a front-end
+utility such as
+.Xr mandoc 1 .
+.
+.Ss Scaling Widths
+Many macros support scaled widths for their arguments, such as
+stipulating a two-inch paragraph indentation with the following:
+.Bd -literal -offset indent
+\&.HP 2i
+.Ed
+.
+.Pp
+The syntax for scaled widths is
+.Sq Li [+-]?[0-9]*.[0-9]*[:unit:]? ,
+where a decimal must be preceded or proceeded by at least one digit.
+Negative numbers, while accepted, are truncated to zero.  The following
+scaling units are accepted:
+.
+.Pp
+.Bl -tag -width Ds -offset indent -compact
+.It c
+centimetre
+.It i
+inch
+.It P
+pica (~1/6 inch)
+.It p
+point (~1/72 inch)
+.It f
+synonym for
+.Sq u
+.It v
+default vertical span
+.It m
+width of rendered
+.Sq m
+.Pq em
+character
+.It n
+width of rendered
+.Sq n
+.Pq en
+character
+.It u
+default horizontal span
+.It M
+mini-em (~1/100 em)
+.El
+.Pp
+Using anything other than
+.Sq m ,
+.Sq n ,
+.Sq u ,
+or
+.Sq v
+is necessarily non-portable across output media.  See
+.Sx COMPATIBILITY .
+.
+.Pp
+If a scaling unit is not provided, the numerical value is interpreted
+under the default rules of
+.Sq v
+for vertical spaces and
+.Sq u
+for horizontal ones.
+.Em Note :
+this differs from
+.Xr mdoc 7 ,
+which, if a unit is not provided, will instead interpret the string as
+literal text.
+.
+.
+.Sh MANUAL STRUCTURE
+Each
+.Nm
+document must contain contains at least the
+.Sx \&TH
+macro describing the document's section and title.  It may occur
+anywhere in the document, although conventionally, it appears as the
+first macro.
+.
+.Pp
+Beyond
+.Sx \&TH ,
+at least one macro or text node must appear in the document.  Documents
+are generally structured as follows:
+.Bd -literal -offset indent
+\&.TH FOO 1 "13 Aug 2009"
+\&.
+\&.SH NAME
+\efBfoo\efR \e(en a description goes here
+\&.\e\*q The next is for sections 2 & 3 only.
+\&.\e\*q .SH LIBRARY
+\&.
+\&.SH SYNOPSIS
+\efBfoo\efR [\efB\e-options\efR] arguments...
+\&.
+\&.SH DESCRIPTION
+The \efBfoo\efR utility processes files...
+\&.
+\&.\e\*q .SH IMPLEMENTATION NOTES
+\&.\e\*q The next is for sections 1 & 8 only.
+\&.\e\*q .SH EXIT STATUS
+\&.\e\*q The next is for sections 2, 3, & 9 only.
+\&.\e\*q .SH RETURN VALUES
+\&.\e\*q The next is for sections 1, 6, 7, & 8 only.
+\&.\e\*q .SH ENVIRONMENT
+\&.\e\*q .SH FILES
+\&.\e\*q .SH EXAMPLES
+\&.\e\*q The next is for sections 1, 4, 6, 7, & 8 only.
+\&.\e\*q .SH DIAGNOSTICS
+\&.\e\*q The next is for sections 2, 3, & 9 only.
+\&.\e\*q .SH ERRORS
+\&.\e\*q .SH SEE ALSO
+\&.\e\*q \efBbar\efR(1)
+\&.\e\*q .SH STANDARDS
+\&.\e\*q .SH HISTORY
+\&.\e\*q .SH AUTHORS
+\&.\e\*q .SH CAVEATS
+\&.\e\*q .SH BUGS
+\&.\e\*q .SH SECURITY CONSIDERATIONS
+.Ed
+.
+.
+.Sh MACRO SYNTAX
+Macros are one to three three characters in length and begin with a
+control character ,
+.Sq \&. ,
+at the beginning of the line.  An arbitrary amount of whitespace may
+sit between the control character and the macro name.  Thus, the
+following are equivalent:
+.Bd -literal -offset indent
+\&.PP
+\&.\ \ \ PP
+.Ed
+.
+.Pp
+The
+.Nm
+macros are classified by scope: line scope or block scope.  Line
+macros are only scoped to the current line (and, in some situations,
+the subsequent line).  Block macros are scoped to the current line and
+subsequent lines until closed by another block macro.
+.
+.
+.Ss Line Macros
+Line macros are generally scoped to the current line, with the body
+consisting of zero or more arguments.  If a macro is scoped to the next
+line and the line arguments are empty, the next line is used instead,
+else the general syntax is used.  Thus:
+.Bd -literal -offset indent
+\&.I
+foo
+.Ed
+.
+.Pp
+is equivalent to
+.Sq \&.I foo .
+If next-line macros are invoked consecutively, only the last is used.
+If a next-line macro is proceded by a block macro, it is ignored.
+.Bd -literal -offset indent
+\&.YO \(lBbody...\(rB
+\(lBbody...\(rB
+.Ed
+.
+.Pp
+.Bl -column -compact -offset indent "MacroX" "ArgumentsX" "ScopeXXXXX"
+.It Em Macro Ta Em Arguments Ta Em Scope
+.It Sx \&B   Ta    n         Ta    next-line
+.It Sx \&BI  Ta    n         Ta    current
+.It Sx \&BR  Ta    n         Ta    current
+.It Sx \&DT  Ta    0         Ta    current
+.It Sx \&I   Ta    n         Ta    next-line
+.It Sx \&IB  Ta    n         Ta    current
+.It Sx \&IR  Ta    n         Ta    current
+.It Sx \&R   Ta    n         Ta    next-line
+.It Sx \&RB  Ta    n         Ta    current
+.It Sx \&RI  Ta    n         Ta    current
+.It Sx \&SB  Ta    n         Ta    next-line
+.It Sx \&SM  Ta    n         Ta    next-line
+.It Sx \&TH  Ta    >1, <6    Ta    current
+.It Sx \&UC  Ta    n         Ta    current
+.It Sx \&br  Ta    0         Ta    current
+.It Sx \&fi  Ta    0         Ta    current
+.It Sx \&i   Ta    n         Ta    current
+.It Sx \&na  Ta    0         Ta    current
+.It Sx \&nf  Ta    0         Ta    current
+.It Sx \&r   Ta    0         Ta    current
+.It Sx \&sp  Ta    1         Ta    current
+.El
+.
+.Pp
+The
+.Sx \&RS ,
+.Sx \&RE ,
+.Sx \&UC ,
+.Sx \&br ,
+.Sx \&fi ,
+.Sx \&i ,
+.Sx \&na ,
+.Sx \&nf ,
+.Sx \&r ,
+and
+.Sx \&sp
+macros should not be used.  They're included for compatibility.
+.
+.
+.Ss Block Macros
+Block macros are comprised of a head and body.  Like for in-line macros,
+the head is scoped to the current line and, in one circumstance, the
+next line; the body is scoped to subsequent lines and is closed out by a
+subsequent block macro invocation.
+.Bd -literal -offset indent
+\&.YO \(lBhead...\(rB
+\(lBhead...\(rB
+\(lBbody...\(rB
+.Ed
+.
+.Pp
+The closure of body scope may be to the section, where a macro is closed
+by
+.Sx \&SH ;
+sub-section, closed by a section or
+.Sx \&SS ;
+part, closed by a section, sub-section, or
+.Sx \&RE ;
+or paragraph, closed by a section, sub-section, part,
+.Sx \&HP ,
+.Sx \&IP ,
+.Sx \&LP ,
+.Sx \&P ,
+.Sx \&PP ,
+or
+.Sx \&TP .
+No closure refers to an explicit block closing macro.
+.
+.Pp
+.Bl -column "MacroX" "ArgumentsX" "Head ScopeX" "sub-sectionX" -compact -offset indent
+.It Em Macro Ta Em Arguments Ta Em Head Scope Ta Em Body Scope
+.It Sx \&HP  Ta    <2        Ta    current    Ta    paragraph
+.It Sx \&IP  Ta    <3        Ta    current    Ta    paragraph
+.It Sx \&LP  Ta    0         Ta    current    Ta    paragraph
+.It Sx \&P   Ta    0         Ta    current    Ta    paragraph
+.It Sx \&PP  Ta    0         Ta    current    Ta    paragraph
+.It Sx \&RE  Ta    0         Ta    current    Ta    none
+.It Sx \&RS  Ta    1         Ta    current    Ta    part
+.It Sx \&SH  Ta    >0        Ta    next-line  Ta    section
+.It Sx \&SS  Ta    >0        Ta    next-line  Ta    sub-section
+.It Sx \&TP  Ta    n         Ta    next-line  Ta    paragraph
+.El
+.
+.Pp
+If a block macro is next-line scoped, it may only be followed by in-line
+macros (excluding
+.Sx \&DT ,
+.Sx \&TH ,
+.Sx \&UC ,
+.Sx \&br ,
+.Sx \&na ,
+.Sx \&sp ,
+.Sx \&nf ,
+and
+.Sx \&fi ) .
+.
+.
+.Sh REFERENCE
+This section is a canonical reference to all macros, arranged
+alphabetically.  For the scoping of individual macros, see
+.Sx MACRO SYNTAX .
+.
+.Ss \&B
+Text is rendered in bold face.
+.Ss \&BI
+Text is rendered alternately in bold face and italic.  Thus,
+.Sq .BI this word and that
+causes
+.Sq this
+and
+.Sq and
+to render in bold face, while
+.Sq word
+and
+.Sq that
+render in italics.  Whitespace between arguments is omitted in output.
+.Ss \&BR
+Text is rendered alternately in bold face and roman (the default font).
+Whitespace between arguments is omitted in output.
+.Ss \&DT
+Has no effect.  Included for compatibility.
+.Ss \&HP
+Begin a paragraph whose initial output line is left-justified, but
+subsequent output lines are indented, with the following syntax:
+.Bd -literal -offset indent
+\&.HP [width]
+.Ed
+.
+.Pp
+If scaling width
+.Va width
+is specified, it's saved for later paragraph left-margins; if
+unspecified, the saved or default width is used.
+.Ss \&I
+Text is rendered in italics.
+.Ss \&IB
+Text is rendered alternately in italics and bold face.  Whitespace
+between arguments is omitted in output.
+.Ss \&IP
+Begin a paragraph with the following syntax:
+.Bd -literal -offset indent
+\&.IP [head [width]]
+.Ed
+.
+.Pp
+This follows the behaviour of the
+.Sx \&TP
+except for the macro syntax (all arguments on the line, instead of
+having next-line scope).  If
+.Va width
+is specified, it's saved for later paragraph left-margins; if
+unspecified, the saved or default width is used.
+.Ss \&IR
+Text is rendered alternately in italics and roman (the default font).
+Whitespace between arguments is omitted in output.
+.Ss \&LP
+Begin an undecorated paragraph.  The scope of a paragraph is closed by a
+subsequent paragraph, sub-section, section, or end of file.  The saved
+paragraph left-margin width is re-set to the default.
+.Ss \&P
+Synonym for
+.Sx \&LP .
+.Ss \&PP
+Synonym for
+.Sx \&LP .
+.Ss \&R
+Text is rendered in roman (the default font).
+.Ss \&RB
+Text is rendered alternately in roman (the default font) and bold face.
+Whitespace between arguments is omitted in output.
+.Ss \&RE
+Explicitly close out the scope of a prior
+.Sx \&RS .
+.Ss \&RI
+Text is rendered alternately in roman (the default font) and italics.
+Whitespace between arguments is omitted in output.
+.Ss \&RS
+Begin a part setting the left margin.  The left margin controls the
+offset, following an initial indentation, to un-indented text such as
+that of
+.Sx \&PP .
+A scaling width may be specified as following:
+.Bd -literal -offset indent
+\&.RS [width]
+.Ed
+.
+.Pp
+If
+.Va width
+is not specified, the saved or default width is used.
+.Ss \&SB
+Text is rendered in small size (one point smaller than the default font)
+bold face.
+.Ss \&SH
+Begin a section.  The scope of a section is only closed by another
+section or the end of file.  The paragraph left-margin width is re-set
+to the default.
+.Ss \&SM
+Text is rendered in small size (one point smaller than the default
+font).
+.Ss \&SS
+Begin a sub-section.  The scope of a sub-section is closed by a
+subsequent sub-section, section, or end of file.  The paragraph
+left-margin width is re-set to the default.
+.Ss \&TH
+Sets the title of the manual page with the following syntax:
+.Bd -literal -offset indent
+\&.TH title section [date [source [volume]]]
+.Ed
+.
+.Pp
+At least the
+.Va title
+and
+.Va section
+arguments must be provided.  The
+.Va date
+argument should be formatted as
+.Qq %b [%d] %Y
+format, described in
+.Xr strptime 3 .
+The
+.Va source
+string specifies the organisation providing the utility.  The
+.Va volume
+replaces the default rendered volume as dictated by the manual section.
+.Ss \&TP
+Begin a paragraph where the head, if exceeding the indentation width, is
+followed by a newline; if not, the body follows on the same line after a
+buffer to the indentation width.  Subsequent output lines are indented.
+.
+.Pp
+The indentation scaling width may be set as follows:
+.Bd -literal -offset indent
+\&.TP [width]
+.Ed
+.
+.Pp
+If
+.Va width
+is specified, it's saved for later paragraph left-margins; if
+unspecified, the saved or default width is used.
+.Ss \&UC
+Has no effect.  Included for compatibility.
+.Ss \&br
+Breaks the current line.  Consecutive invocations have no further effect.
+.Ss \&fi
+End literal mode begun by
+.Sx \&nf .
+.Ss \&i
+Italicise arguments.  If no arguments are specified, all subsequent text
+is italicised.
+.Ss \&na
+Don't align to the right margin.
+.Ss \&nf
+Begin literal mode: all subsequent free-form lines have their end of
+line boundaries preserved.  May be ended by
+.Sx \&fi .
+.Ss \&r
+Fonts and styles (bold face, italics) reset to roman (default font).
+.Ss \&sp
+Insert n spaces, where n is the macro's positive numeric argument.  If
+0, this is equivalent to the
+.Sx \&br
+macro.
+.
+.
+.Sh COMPATIBILITY
+This section documents compatibility with other roff implementations, at
+this time limited to
+.Xr groff 1 .
+.Bl -hyphen
+.It
+In quoted literals, groff allowed pair-wise double-quotes to produce a
+standalone double-quote in formatted output.  This idiosyncratic
+behaviour is no longer applicable.
+.It
+The
+.Sq sp
+macro does not accept negative numbers.
+.It
+Blocks of whitespace are stripped from both macro and free-form text
+lines (except when in literal mode), while groff would retain whitespace
+in free-form text lines.
+.El
+.
+.
+.Sh SEE ALSO
+.Xr mandoc 1 ,
+.Xr mandoc_char 7
+.
+.
+.Sh AUTHORS
+The
+.Nm
+reference was written by
+.An Kristaps Dzonsons Aq kristaps@kth.se .
+.
+.
+.Sh CAVEATS
+Do not use this language.  Use
+.Xr mdoc 7 ,
+instead.
+.
diff --git a/usr.bin/mandoc/man.c b/usr.bin/mandoc/man.c
new file mode 100644 (file)
index 0000000..5356635
--- /dev/null
@@ -0,0 +1,628 @@
+/*     $Id: man.c,v 1.14 2009/10/19 10:20:24 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009 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 <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "libman.h"
+
+const  char *const __man_merrnames[WERRMAX] = {
+       "invalid character", /* WNPRINT */
+       "system: malloc error", /* WNMEM */
+       "invalid manual section", /* WMSEC */
+       "invalid date format", /* WDATE */
+       "scope of prior line violated", /* WLNSCOPE */
+       "trailing whitespace", /* WTSPACE */
+       "unterminated quoted parameter", /* WTQUOTE */
+       "document has no body", /* WNODATA */
+       "document has no title/section", /* WNOTITLE */
+       "invalid escape sequence", /* WESCAPE */
+       "invalid number format", /* WNUMFMT */
+       "expected block head arguments", /* WHEADARGS */
+       "expected block body arguments", /* WBODYARGS */
+       "expected empty block head", /* WNHEADARGS */
+       "unknown macro", /* WMACRO */
+       "ill-formed macro", /* WMACROFORM */
+       "scope open on exit", /* WEXITSCOPE */
+       "no scope context", /* WNOSCOPE */
+       "literal context already open", /* WOLITERAL */
+       "no literal context open" /* WNLITERAL */
+};
+
+const  char *const __man_macronames[MAN_MAX] = {
+       "br",           "TH",           "SH",           "SS",
+       "TP",           "LP",           "PP",           "P",
+       "IP",           "HP",           "SM",           "SB",
+       "BI",           "IB",           "BR",           "RB",
+       "R",            "B",            "I",            "IR",
+       "RI",           "na",           "i",            "sp",
+       "nf",           "fi",           "r",            "RE",
+       "RS",           "DT",           "UC"
+       };
+
+const  char * const *man_macronames = __man_macronames;
+
+static struct man_node *man_node_alloc(int, int,
+                               enum man_type, int);
+static int              man_node_append(struct man *,
+                               struct man_node *);
+static int              man_ptext(struct man *, int, char *);
+static int              man_pmacro(struct man *, int, char *);
+static void             man_free1(struct man *);
+static int              man_alloc1(struct man *);
+static int              pstring(struct man *, int, int,
+                               const char *, size_t);
+
+
+const struct man_node *
+man_node(const struct man *m)
+{
+
+       return(MAN_HALT & m->flags ? NULL : m->first);
+}
+
+
+const struct man_meta *
+man_meta(const struct man *m)
+{
+
+       return(MAN_HALT & m->flags ? NULL : &m->meta);
+}
+
+
+int
+man_reset(struct man *man)
+{
+
+       man_free1(man);
+       return(man_alloc1(man));
+}
+
+
+void
+man_free(struct man *man)
+{
+
+       man_free1(man);
+       free(man);
+}
+
+
+struct man *
+man_alloc(void *data, int pflags, const struct man_cb *cb)
+{
+       struct man      *p;
+
+       if (NULL == (p = calloc(1, sizeof(struct man))))
+               return(NULL);
+
+       if ( ! man_alloc1(p)) {
+               free(p);
+               return(NULL);
+       }
+
+       man_hash_init();
+
+       p->data = data;
+       p->pflags = pflags;
+       (void)memcpy(&p->cb, cb, sizeof(struct man_cb));
+       return(p);
+}
+
+
+int
+man_endparse(struct man *m)
+{
+
+       if (MAN_HALT & m->flags)
+               return(0);
+       else if (man_macroend(m))
+               return(1);
+       m->flags |= MAN_HALT;
+       return(0);
+}
+
+
+int
+man_parseln(struct man *m, int ln, char *buf)
+{
+
+       return('.' == *buf ?
+                       man_pmacro(m, ln, buf) :
+                       man_ptext(m, ln, buf));
+}
+
+
+static void
+man_free1(struct man *man)
+{
+
+       if (man->first)
+               man_node_freelist(man->first);
+       if (man->meta.title)
+               free(man->meta.title);
+       if (man->meta.source)
+               free(man->meta.source);
+       if (man->meta.vol)
+               free(man->meta.vol);
+}
+
+
+static int
+man_alloc1(struct man *m)
+{
+
+       bzero(&m->meta, sizeof(struct man_meta));
+       m->flags = 0;
+       m->last = calloc(1, sizeof(struct man_node));
+       if (NULL == m->last)
+               return(0);
+       m->first = m->last;
+       m->last->type = MAN_ROOT;
+       m->next = MAN_NEXT_CHILD;
+       return(1);
+}
+
+
+static int
+man_node_append(struct man *man, struct man_node *p)
+{
+
+       assert(man->last);
+       assert(man->first);
+       assert(MAN_ROOT != p->type);
+
+       switch (man->next) {
+       case (MAN_NEXT_SIBLING):
+               man->last->next = p;
+               p->prev = man->last;
+               p->parent = man->last->parent;
+               break;
+       case (MAN_NEXT_CHILD):
+               man->last->child = p;
+               p->parent = man->last;
+               break;
+       default:
+               abort();
+               /* NOTREACHED */
+       }
+
+       p->parent->nchild++;
+
+       if ( ! man_valid_pre(man, p))
+               return(0);
+
+       switch (p->type) {
+       case (MAN_HEAD):
+               assert(MAN_BLOCK == p->parent->type);
+               p->parent->head = p;
+               break;
+       case (MAN_BODY):
+               assert(MAN_BLOCK == p->parent->type);
+               p->parent->body = p;
+               break;
+       default:
+               break;
+       }
+
+       man->last = p;
+
+       switch (p->type) {
+       case (MAN_TEXT):
+               if ( ! man_valid_post(man))
+                       return(0);
+               if ( ! man_action_post(man))
+                       return(0);
+               break;
+       default:
+               break;
+       }
+
+       return(1);
+}
+
+
+static struct man_node *
+man_node_alloc(int line, int pos, enum man_type type, int tok)
+{
+       struct man_node *p;
+
+       p = calloc(1, sizeof(struct man_node));
+       if (NULL == p)
+               return(NULL);
+
+       p->line = line;
+       p->pos = pos;
+       p->type = type;
+       p->tok = tok;
+       return(p);
+}
+
+
+int
+man_elem_alloc(struct man *m, int line, int pos, int tok)
+{
+       struct man_node *p;
+
+       p = man_node_alloc(line, pos, MAN_ELEM, tok);
+       if (NULL == p)
+               return(0);
+       if ( ! man_node_append(m, p))
+               return(0);
+       m->next = MAN_NEXT_CHILD;
+       return(1);
+}
+
+
+int
+man_head_alloc(struct man *m, int line, int pos, int tok)
+{
+       struct man_node *p;
+
+       p = man_node_alloc(line, pos, MAN_HEAD, tok);
+       if (NULL == p)
+               return(0);
+       if ( ! man_node_append(m, p))
+               return(0);
+       m->next = MAN_NEXT_CHILD;
+       return(1);
+}
+
+
+int
+man_body_alloc(struct man *m, int line, int pos, int tok)
+{
+       struct man_node *p;
+
+       p = man_node_alloc(line, pos, MAN_BODY, tok);
+       if (NULL == p)
+               return(0);
+       if ( ! man_node_append(m, p))
+               return(0);
+       m->next = MAN_NEXT_CHILD;
+       return(1);
+}
+
+
+int
+man_block_alloc(struct man *m, int line, int pos, int tok)
+{
+       struct man_node *p;
+
+       p = man_node_alloc(line, pos, MAN_BLOCK, tok);
+       if (NULL == p)
+               return(0);
+       if ( ! man_node_append(m, p))
+               return(0);
+       m->next = MAN_NEXT_CHILD;
+       return(1);
+}
+
+
+static int
+pstring(struct man *m, int line, int pos,
+               const char *p, size_t len)
+{
+       struct man_node *n;
+       size_t           sv;
+
+       n = man_node_alloc(line, pos, MAN_TEXT, -1);
+       if (NULL == n)
+               return(0);
+
+       n->string = malloc(len + 1);
+       if (NULL == n->string) {
+               free(n);
+               return(0);
+       }
+
+       sv = strlcpy(n->string, p, len + 1);
+
+       /* Prohibit truncation. */
+       assert(sv < len + 1);
+
+       if ( ! man_node_append(m, n))
+               return(0);
+       m->next = MAN_NEXT_SIBLING;
+       return(1);
+}
+
+
+int
+man_word_alloc(struct man *m, int line, int pos, const char *word)
+{
+
+       return(pstring(m, line, pos, word, strlen(word)));
+}
+
+
+void
+man_node_free(struct man_node *p)
+{
+
+       if (p->string)
+               free(p->string);
+       if (p->parent)
+               p->parent->nchild--;
+       free(p);
+}
+
+
+void
+man_node_freelist(struct man_node *p)
+{
+       struct man_node *n;
+
+       if (p->child)
+               man_node_freelist(p->child);
+       assert(0 == p->nchild);
+       n = p->next;
+       man_node_free(p);
+       if (n)
+               man_node_freelist(n);
+}
+
+
+static int
+man_ptext(struct man *m, int line, char *buf)
+{
+       int              i, j;
+
+       /* Literal free-form text whitespace is preserved. */
+
+       if (MAN_LITERAL & m->flags) {
+               if ( ! man_word_alloc(m, line, 0, buf))
+                       return(0);
+               goto descope;
+       }
+
+       /* First de-chunk and allocate words. */
+
+       for (i = 0; ' ' == buf[i]; i++)
+               /* Skip leading whitespace. */ ;
+       if (0 == buf[i]) {
+               if ( ! pstring(m, line, 0, &buf[i], 0))
+                       return(0);
+               goto descope;
+       }
+
+       for (j = i; buf[i]; i++) {
+               if (' ' != buf[i])
+                       continue;
+
+               /* Escaped whitespace. */
+               if (i && ' ' == buf[i] && '\\' == buf[i - 1])
+                       continue;
+
+               buf[i++] = 0;
+               if ( ! pstring(m, line, j, &buf[j], (size_t)(i - j)))
+                       return(0);
+
+               for ( ; ' ' == buf[i]; i++)
+                       /* Skip trailing whitespace. */ ;
+
+               j = i;
+               if (0 == buf[i])
+                       break;
+       }
+
+       if (j != i && ! pstring(m, line, j, &buf[j], (size_t)(i - j)))
+               return(0);
+
+descope:
+
+       /*
+        * Co-ordinate what happens with having a next-line scope open:
+        * first close out the element scope (if applicable), then close
+        * out the block scope (also if applicable).
+        */
+
+       if (MAN_ELINE & m->flags) {
+               m->flags &= ~MAN_ELINE;
+               if ( ! man_unscope(m, m->last->parent))
+                       return(0);
+       }
+
+       if ( ! (MAN_BLINE & m->flags))
+               return(1);
+       m->flags &= ~MAN_BLINE;
+
+       if ( ! man_unscope(m, m->last->parent))
+               return(0);
+       return(man_body_alloc(m, line, 0, m->last->tok));
+}
+
+
+int
+man_pmacro(struct man *m, int ln, char *buf)
+{
+       int              i, j, c, ppos, fl;
+       char             mac[5];
+       struct man_node *n;
+
+       /* Comments and empties are quickly ignored. */
+
+       fl = m->flags;
+
+       if (0 == buf[1])
+               goto out;
+
+       i = 1;
+
+       if (' ' == buf[i]) {
+               i++;
+               while (buf[i] && ' ' == buf[i])
+                       i++;
+               if (0 == buf[i])
+                       goto out;
+       }
+
+       ppos = i;
+
+       /* Copy the first word into a nil-terminated buffer. */
+
+       for (j = 0; j < 4; j++, i++) {
+               if (0 == (mac[j] = buf[i]))
+                       break;
+               else if (' ' == buf[i])
+                       break;
+
+               /* Check for invalid characters. */
+
+               if (isgraph((u_char)buf[i]))
+                       continue;
+               return(man_perr(m, ln, i, WNPRINT));
+       }
+
+       mac[j] = 0;
+
+       if (j == 4 || j < 1) {
+               if ( ! (MAN_IGN_MACRO & m->pflags)) {
+                       (void)man_perr(m, ln, ppos, WMACROFORM);
+                       goto err;
+               }
+               if ( ! man_pwarn(m, ln, ppos, WMACROFORM))
+                       goto err;
+               return(1);
+       }
+
+       if (MAN_MAX == (c = man_hash_find(mac))) {
+               if ( ! (MAN_IGN_MACRO & m->pflags)) {
+                       (void)man_perr(m, ln, ppos, WMACRO);
+                       goto err;
+               }
+               if ( ! man_pwarn(m, ln, ppos, WMACRO))
+                       goto err;
+               return(1);
+       }
+
+       /* The macro is sane.  Jump to the next word. */
+
+       while (buf[i] && ' ' == buf[i])
+               i++;
+
+       /* Remove prior ELINE macro, if applicable. */
+
+       if (m->flags & MAN_ELINE) {
+               n = m->last;
+               assert(NULL == n->child);
+               assert(0 == n->nchild);
+               if ( ! man_nwarn(m, n, WLNSCOPE))
+                       return(0);
+
+               if (n->prev) {
+                       assert(n != n->parent->child);
+                       assert(n == n->prev->next);
+                       n->prev->next = NULL;
+                       m->last = n->prev;
+                       m->next = MAN_NEXT_SIBLING;
+               } else {
+                       assert(n == n->parent->child);
+                       n->parent->child = NULL;
+                       m->last = n->parent;
+                       m->next = MAN_NEXT_CHILD;
+               }
+
+               man_node_free(n);
+               m->flags &= ~MAN_ELINE;
+       }
+
+       /* Begin recursive parse sequence. */
+
+       assert(man_macros[c].fp);
+
+       if ( ! (*man_macros[c].fp)(m, c, ln, ppos, &i, buf))
+               goto err;
+
+out:
+       if ( ! (MAN_BLINE & fl))
+               return(1);
+
+       /*
+        * If we've opened a new next-line element scope, then return
+        * now, as the next line will close out the block scope.
+        */
+
+       if (MAN_ELINE & m->flags)
+               return(1);
+
+       /* Close out the block scope opened in the prior line.  */
+
+       assert(MAN_BLINE & m->flags);
+       m->flags &= ~MAN_BLINE;
+
+       if ( ! man_unscope(m, m->last->parent))
+               return(0);
+       return(man_body_alloc(m, ln, 0, m->last->tok));
+
+err:   /* Error out. */
+
+       m->flags |= MAN_HALT;
+       return(0);
+}
+
+
+int
+man_verr(struct man *man, int ln, int pos, const char *fmt, ...)
+{
+       char             buf[256];
+       va_list          ap;
+
+       if (NULL == man->cb.man_err)
+               return(0);
+
+       va_start(ap, fmt);
+       (void)vsnprintf(buf, sizeof(buf) - 1, fmt, ap);
+       va_end(ap);
+       return((*man->cb.man_err)(man->data, ln, pos, buf));
+}
+
+
+int
+man_vwarn(struct man *man, int ln, int pos, const char *fmt, ...)
+{
+       char             buf[256];
+       va_list          ap;
+
+       if (NULL == man->cb.man_warn)
+               return(0);
+
+       va_start(ap, fmt);
+       (void)vsnprintf(buf, sizeof(buf) - 1, fmt, ap);
+       va_end(ap);
+       return((*man->cb.man_warn)(man->data, ln, pos, buf));
+}
+
+
+int
+man_err(struct man *m, int line, int pos, int iserr, enum merr type)
+{
+       const char       *p;
+
+       p = __man_merrnames[(int)type];
+       assert(p);
+
+       if (iserr)
+               return(man_verr(m, line, pos, p));
+
+       return(man_vwarn(m, line, pos, p));
+}
diff --git a/usr.bin/mandoc/man.h b/usr.bin/mandoc/man.h
new file mode 100644 (file)
index 0000000..deb7b89
--- /dev/null
@@ -0,0 +1,116 @@
+/*     $Id: man.h,v 1.10 2009/10/19 21:08:58 schwarze Exp $ */
+/*
+ * Copyright (c) 2009 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.
+ */
+#ifndef MAN_H
+#define MAN_H
+
+#include <time.h>
+
+#define        MAN_br           0
+#define        MAN_TH           1
+#define        MAN_SH           2
+#define        MAN_SS           3
+#define        MAN_TP           4
+#define        MAN_LP           5
+#define        MAN_PP           6
+#define        MAN_P            7
+#define        MAN_IP           8
+#define        MAN_HP           9
+#define        MAN_SM           10
+#define        MAN_SB           11
+#define        MAN_BI           12
+#define        MAN_IB           13
+#define        MAN_BR           14
+#define        MAN_RB           15
+#define        MAN_R            16
+#define        MAN_B            17
+#define        MAN_I            18
+#define        MAN_IR           19
+#define        MAN_RI           20
+#define        MAN_na           21
+#define        MAN_i            22
+#define        MAN_sp           23
+#define        MAN_nf           24
+#define        MAN_fi           25
+#define        MAN_r            26
+#define        MAN_RE           27
+#define        MAN_RS           28
+#define        MAN_DT           29
+#define        MAN_UC           30
+#define        MAN_MAX          31
+
+enum   man_type {
+       MAN_TEXT,
+       MAN_ELEM,
+       MAN_ROOT,
+       MAN_BLOCK,
+       MAN_HEAD,
+       MAN_BODY
+};
+
+struct man_meta {
+       int              msec;
+       time_t           date;
+       char            *vol;
+       char            *title;
+       char            *source;
+};
+
+struct man_node {
+       struct man_node *parent;
+       struct man_node *child;
+       struct man_node *next;
+       struct man_node *prev;
+       int              nchild;
+       int              line;
+       int              pos;
+       int              tok;
+       int              flags;
+#define        MAN_VALID       (1 << 0)
+#define        MAN_ACTED       (1 << 1)
+       enum man_type    type;
+       char            *string;
+       struct man_node *head;
+       struct man_node *body;
+};
+
+#define        MAN_IGN_MACRO    (1 << 0)
+#define        MAN_IGN_CHARS    (1 << 1)
+#define        MAN_IGN_ESCAPE   (1 << 2)
+
+extern const char *const *man_macronames;
+
+struct man_cb {
+       int     (*man_warn)(void *, int, int, const char *);
+       int     (*man_err)(void *, int, int, const char *);
+};
+
+__BEGIN_DECLS
+
+struct man;
+
+void             man_free(struct man *);
+struct man      *man_alloc(void *, int, const struct man_cb *);
+int              man_reset(struct man *);
+int              man_parseln(struct man *, int, char *buf);
+int              man_endparse(struct man *);
+
+const struct man_node *man_node(const struct man *);
+const struct man_meta *man_meta(const struct man *);
+
+__END_DECLS
+
+#endif /*!MAN_H*/
diff --git a/usr.bin/mandoc/man_action.c b/usr.bin/mandoc/man_action.c
new file mode 100644 (file)
index 0000000..a7870ad
--- /dev/null
@@ -0,0 +1,219 @@
+/*     $Id: man_action.c,v 1.8 2009/09/18 22:46:14 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009 Kristaps Dzonsons <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/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 int       post_fi(struct man *);
+static int       post_nf(struct man *);
+
+const  struct actions man_actions[MAN_MAX] = {
+       { NULL }, /* br */
+       { post_TH }, /* TH */
+       { NULL }, /* SH */
+       { NULL }, /* SS */
+       { NULL }, /* TP */
+       { NULL }, /* LP */
+       { NULL }, /* PP */
+       { NULL }, /* P */
+       { NULL }, /* IP */
+       { NULL }, /* HP */
+       { NULL }, /* SM */
+       { NULL }, /* SB */
+       { NULL }, /* BI */
+       { NULL }, /* IB */
+       { NULL }, /* BR */
+       { NULL }, /* RB */
+       { NULL }, /* R */
+       { NULL }, /* B */
+       { NULL }, /* I */
+       { NULL }, /* IR */
+       { NULL }, /* RI */
+       { NULL }, /* na */
+       { NULL }, /* i */
+       { NULL }, /* sp */
+       { post_nf }, /* nf */
+       { post_fi }, /* fi */
+       { NULL }, /* r */
+       { NULL }, /* RE */
+       { NULL }, /* RS */
+       { NULL }, /* DT */
+       { NULL }, /* UC */
+};
+
+static time_t    man_atotime(const char *);
+
+
+int
+man_action_post(struct man *m)
+{
+
+       if (MAN_ACTED & m->last->flags)
+               return(1);
+       m->last->flags |= MAN_ACTED;
+
+       switch (m->last->type) {
+       case (MAN_TEXT):
+               /* FALLTHROUGH */
+       case (MAN_ROOT):
+               return(1);
+       default:
+               break;
+       }
+
+       if (NULL == man_actions[m->last->tok].post)
+               return(1);
+       return((*man_actions[m->last->tok].post)(m));
+}
+
+
+static int
+post_fi(struct man *m)
+{
+
+       if ( ! (MAN_LITERAL & m->flags))
+               if ( ! man_nwarn(m, m->last, WNLITERAL))
+                       return(0);
+       m->flags &= ~MAN_LITERAL;
+       return(1);
+}
+
+
+static int
+post_nf(struct man *m)
+{
+
+       if (MAN_LITERAL & m->flags)
+               if ( ! man_nwarn(m, m->last, WOLITERAL))
+                       return(0);
+       m->flags |= MAN_LITERAL;
+       return(1);
+}
+
+
+static int
+post_TH(struct man *m)
+{
+       struct man_node *n;
+       char            *ep;
+       long             lval;
+
+       if (m->meta.title)
+               free(m->meta.title);
+       if (m->meta.vol)
+               free(m->meta.vol);
+       if (m->meta.source)
+               free(m->meta.source);
+
+       m->meta.title = m->meta.vol = m->meta.source = NULL;
+       m->meta.msec = 0;
+       m->meta.date = 0;
+
+       /* ->TITLE<- MSEC DATE SOURCE VOL */
+
+       n = m->last->child;
+       assert(n);
+
+       if (NULL == (m->meta.title = strdup(n->string)))
+               return(man_nerr(m, n, WNMEM));
+
+       /* TITLE ->MSEC<- DATE SOURCE VOL */
+
+       n = n->next;
+       assert(n);
+
+       errno = 0;
+       lval = strtol(n->string, &ep, 10);
+       if (n->string[0] != '\0' && *ep == '\0')
+               m->meta.msec = (int)lval;
+       else if ( ! man_nwarn(m, n, WMSEC))
+               return(0);
+
+       /* TITLE MSEC ->DATE<- SOURCE VOL */
+
+       if (NULL == (n = n->next))
+               m->meta.date = time(NULL);
+       else if (0 == (m->meta.date = man_atotime(n->string))) {
+               if ( ! man_nwarn(m, n, WDATE))
+                       return(0);
+               m->meta.date = time(NULL);
+       }
+
+       /* TITLE MSEC DATE ->SOURCE<- VOL */
+
+       if (n && (n = n->next))
+               if (NULL == (m->meta.source = strdup(n->string)))
+                       return(man_nerr(m, n, WNMEM));
+
+       /* TITLE MSEC DATE SOURCE ->VOL<- */
+
+       if (n && (n = n->next))
+               if (NULL == (m->meta.vol = strdup(n->string)))
+                       return(man_nerr(m, n, WNMEM));
+
+       /*
+        * The end document shouldn't have the prologue macros as part
+        * of the syntax tree (they encompass only meta-data).
+        */
+
+       if (m->last->parent->child == m->last) {
+               m->last->parent->child = NULL;
+               n = m->last;
+               m->last = m->last->parent;
+               m->next = MAN_NEXT_CHILD;
+       } else {
+               assert(m->last->prev);
+               m->last->prev->next = NULL;
+               n = m->last;
+               m->last = m->last->prev;
+               m->next = MAN_NEXT_SIBLING;
+       }
+
+       man_node_freelist(n);
+       return(1);
+}
+
+
+static time_t
+man_atotime(const char *p)
+{
+       struct tm        tm;
+       char            *pp;
+
+       bzero(&tm, sizeof(struct tm));
+
+       if ((pp = strptime(p, "%b %d %Y", &tm)) && 0 == *pp)
+               return(mktime(&tm));
+       if ((pp = strptime(p, "%d %b %Y", &tm)) && 0 == *pp)
+               return(mktime(&tm));
+       if ((pp = strptime(p, "%b %d, %Y", &tm)) && 0 == *pp)
+               return(mktime(&tm));
+       if ((pp = strptime(p, "%b %Y", &tm)) && 0 == *pp)
+               return(mktime(&tm));
+
+       return(0);
+}
diff --git a/usr.bin/mandoc/man_argv.c b/usr.bin/mandoc/man_argv.c
new file mode 100644 (file)
index 0000000..6d5b35b
--- /dev/null
@@ -0,0 +1,98 @@
+/*     $Id: man_argv.c,v 1.1 2009/08/23 11:22:19 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009 Kristaps Dzonsons <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 <stdlib.h>
+#include <string.h>
+
+#include "libman.h"
+
+
+int
+man_args(struct man *m, int line, int *pos, char *buf, char **v)
+{
+
+       assert(*pos);
+       assert(' ' != buf[*pos]);
+
+       if (0 == buf[*pos])
+               return(ARGS_EOLN);
+
+       *v = &buf[*pos];
+
+       /*
+        * Process a quoted literal.  A quote begins with a double-quote
+        * and ends with a double-quote NOT preceded by a double-quote.
+        * Whitespace is NOT involved in literal termination.
+        */
+
+       if ('\"' == buf[*pos]) {
+               *v = &buf[++(*pos)];
+
+               for ( ; buf[*pos]; (*pos)++) {
+                       if ('\"' != buf[*pos])
+                               continue;
+                       if ('\"' != buf[*pos + 1])
+                               break;
+                       (*pos)++;
+               }
+
+               if (0 == buf[*pos]) {
+                       if ( ! man_pwarn(m, line, *pos, WTQUOTE))
+                               return(ARGS_ERROR);
+                       return(ARGS_QWORD);
+               }
+
+               buf[(*pos)++] = 0;
+
+               if (0 == buf[*pos])
+                       return(ARGS_QWORD);
+
+               while (' ' == buf[*pos])
+                       (*pos)++;
+
+               if (0 == buf[*pos])
+                       if ( ! man_pwarn(m, line, *pos, WTSPACE))
+                               return(ARGS_ERROR);
+
+               return(ARGS_QWORD);
+       }
+
+       /*
+        * A non-quoted term progresses until either the end of line or
+        * a non-escaped whitespace.
+        */
+
+       for ( ; buf[*pos]; (*pos)++)
+               if (' ' == buf[*pos] && '\\' != buf[*pos - 1])
+                       break;
+
+       if (0 == buf[*pos])
+               return(ARGS_WORD);
+
+       buf[(*pos)++] = 0;
+
+       while (' ' == buf[*pos])
+               (*pos)++;
+
+       if (0 == buf[*pos])
+               if ( ! man_pwarn(m, line, *pos, WTSPACE))
+                       return(ARGS_ERROR);
+
+       return(ARGS_WORD);
+}
diff --git a/usr.bin/mandoc/man_hash.c b/usr.bin/mandoc/man_hash.c
new file mode 100644 (file)
index 0000000..506f997
--- /dev/null
@@ -0,0 +1,77 @@
+/*     $Id: man_hash.c,v 1.7 2009/10/19 10:20:24 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009 Kristaps Dzonsons <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 <limits.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "libman.h"
+
+static u_char          table[26 * 6];
+
+/*
+ * XXX - this hash has global scope, so if intended for use as a library
+ * with multiple callers, it will need re-invocation protection.
+ */
+void
+man_hash_init(void)
+{
+       int              i, j, x;
+
+       memset(table, UCHAR_MAX, sizeof(table));
+
+       for (i = 0; i < MAN_MAX; i++) {
+               x = man_macronames[i][0];
+               assert((x >= 65 && x <= 90) ||
+                               (x >= 97 && x <= 122));
+
+               x -= (x <= 90) ? 65 : 97;
+               x *= 6;
+
+               for (j = 0; j < 6; j++)
+                       if (UCHAR_MAX == table[x + j]) {
+                               table[x + j] = (u_char)i;
+                               break;
+                       }
+               assert(j < 6);
+       }
+}
+
+int
+man_hash_find(const char *tmp)
+{
+       int              x, i, tok;
+
+       if (0 == (x = tmp[0]))
+               return(MAN_MAX);
+       if ( ! ((x >= 65 && x <= 90) || (x >= 97 && x <= 122)))
+               return(MAN_MAX);
+
+       x -= (x <= 90) ? 65 : 97;
+       x *= 6;
+
+       for (i = 0; i < 6; i++) {
+               if (UCHAR_MAX == (tok = table[x + i]))
+                       return(MAN_MAX);
+               if (0 == strcmp(tmp, man_macronames[tok]))
+                       return(tok);
+       }
+
+       return(MAN_MAX);
+}
diff --git a/usr.bin/mandoc/man_html.c b/usr.bin/mandoc/man_html.c
new file mode 100644 (file)
index 0000000..63152bc
--- /dev/null
@@ -0,0 +1,696 @@
+/*     $Id: man_html.c,v 1.1 2009/10/21 19:13:50 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009 Kristaps Dzonsons <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 <sys/queue.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "out.h"
+#include "html.h"
+#include "man.h"
+#include "main.h"
+
+/* TODO: preserve ident widths. */
+
+#define        INDENT            5
+#define        HALFINDENT        3
+
+#define        MAN_ARGS          const struct man_meta *m, \
+                         const struct man_node *n, \
+                         struct html *h
+
+struct htmlman {
+       int             (*pre)(MAN_ARGS);
+       int             (*post)(MAN_ARGS);
+};
+
+static void              print_man(MAN_ARGS);
+static void              print_man_head(MAN_ARGS);
+static void              print_man_nodelist(MAN_ARGS);
+static void              print_man_node(MAN_ARGS);
+
+static int               a2width(const struct man_node *,
+                               struct roffsu *);
+
+static int               man_alt_pre(MAN_ARGS);
+static int               man_br_pre(MAN_ARGS);
+static int               man_ign_pre(MAN_ARGS);
+static void              man_root_post(MAN_ARGS);
+static int               man_root_pre(MAN_ARGS);
+static int               man_B_pre(MAN_ARGS);
+static int               man_HP_pre(MAN_ARGS);
+static int               man_I_pre(MAN_ARGS);
+static int               man_IP_pre(MAN_ARGS);
+static int               man_PP_pre(MAN_ARGS);
+static int               man_RS_pre(MAN_ARGS);
+static int               man_SB_pre(MAN_ARGS);
+static int               man_SH_pre(MAN_ARGS);
+static int               man_SM_pre(MAN_ARGS);
+static int               man_SS_pre(MAN_ARGS);
+
+static const struct htmlman mans[MAN_MAX] = {
+       { man_br_pre, NULL }, /* br */
+       { NULL, NULL }, /* TH */
+       { man_SH_pre, NULL }, /* SH */
+       { man_SS_pre, NULL }, /* SS */
+       { man_IP_pre, NULL }, /* TP */
+       { man_PP_pre, NULL }, /* LP */
+       { man_PP_pre, NULL }, /* PP */
+       { man_PP_pre, NULL }, /* P */
+       { man_IP_pre, NULL }, /* IP */
+       { man_HP_pre, NULL }, /* HP */
+       { man_SM_pre, NULL }, /* SM */
+       { man_SB_pre, NULL }, /* SB */
+       { man_alt_pre, NULL }, /* BI */
+       { man_alt_pre, NULL }, /* IB */
+       { man_alt_pre, NULL }, /* BR */
+       { man_alt_pre, NULL }, /* RB */
+       { NULL, NULL }, /* R */
+       { man_B_pre, NULL }, /* B */
+       { man_I_pre, NULL }, /* I */
+       { man_alt_pre, NULL }, /* IR */
+       { man_alt_pre, NULL }, /* RI */
+       { NULL, NULL }, /* na */
+       { NULL, NULL }, /* i */
+       { man_br_pre, NULL }, /* sp */
+       { NULL, NULL }, /* nf */
+       { NULL, NULL }, /* fi */
+       { NULL, NULL }, /* r */
+       { NULL, NULL }, /* RE */
+       { man_RS_pre, NULL }, /* RS */
+       { man_ign_pre, NULL }, /* DT */
+       { man_ign_pre, NULL }, /* UC */
+};
+
+
+void
+html_man(void *arg, const struct man *m)
+{
+       struct html     *h;
+       struct tag      *t;
+
+       h = (struct html *)arg;
+
+       print_gen_doctype(h);
+
+       t = print_otag(h, TAG_HTML, 0, NULL);
+       print_man(man_meta(m), man_node(m), h);
+       print_tagq(h, t);
+
+       printf("\n");
+}
+
+
+static void
+print_man(MAN_ARGS)
+{
+       struct tag      *t;
+       struct htmlpair  tag;
+
+       t = print_otag(h, TAG_HEAD, 0, NULL);
+
+       print_man_head(m, n, h);
+       print_tagq(h, t);
+       t = print_otag(h, TAG_BODY, 0, NULL);
+
+       tag.key = ATTR_CLASS;
+       tag.val = "body";
+       print_otag(h, TAG_DIV, 1, &tag);
+
+       print_man_nodelist(m, n, h);
+
+       print_tagq(h, t);
+}
+
+
+/* ARGSUSED */
+static void
+print_man_head(MAN_ARGS)
+{
+
+       print_gen_head(h);
+       bufinit(h);
+       buffmt(h, "%s(%d)", m->title, m->msec);
+
+       print_otag(h, TAG_TITLE, 0, NULL);
+       print_text(h, h->buf);
+}
+
+
+static void
+print_man_nodelist(MAN_ARGS)
+{
+
+       print_man_node(m, n, h);
+       if (n->next)
+               print_man_nodelist(m, n->next, h);
+}
+
+
+static void
+print_man_node(MAN_ARGS)
+{
+       int              child;
+       struct tag      *t;
+
+       child = 1;
+       t = SLIST_FIRST(&h->tags);
+
+       bufinit(h);
+
+       switch (n->type) {
+       case (MAN_ROOT):
+               child = man_root_pre(m, n, h);
+               break;
+       case (MAN_TEXT):
+               print_text(h, n->string);
+               break;
+       default:
+               if (mans[n->tok].pre)
+                       child = (*mans[n->tok].pre)(m, n, h);
+               break;
+       }
+
+       if (child && n->child)
+               print_man_nodelist(m, n->child, h);
+
+       print_stagq(h, t);
+
+       bufinit(h);
+
+       switch (n->type) {
+       case (MAN_ROOT):
+               man_root_post(m, n, h);
+               break;
+       case (MAN_TEXT):
+               break;
+       default:
+               if (mans[n->tok].post)
+                       (*mans[n->tok].post)(m, n, h);
+               break;
+       }
+}
+
+
+static int
+a2width(const struct man_node *n, struct roffsu *su)
+{
+
+       if (MAN_TEXT != n->type)
+               return(0);
+       if (a2roffsu(n->string, su, SCALE_BU))
+               return(1);
+
+       return(0);
+}
+
+
+/* ARGSUSED */
+static int
+man_root_pre(MAN_ARGS)
+{
+       struct htmlpair  tag[2];
+       struct tag      *t, *tt;
+       char             b[BUFSIZ], title[BUFSIZ];
+
+       b[0] = 0;
+       if (m->vol)
+               (void)strlcat(b, m->vol, BUFSIZ);
+
+       (void)snprintf(title, BUFSIZ - 1,
+                       "%s(%d)", m->title, m->msec);
+
+       PAIR_CLASS_INIT(&tag[0], "header");
+       bufcat_style(h, "width", "100%");
+       PAIR_STYLE_INIT(&tag[1], h);
+       t = print_otag(h, TAG_TABLE, 2, tag);
+       tt = print_otag(h, TAG_TR, 0, NULL);
+
+       bufinit(h);
+       bufcat_style(h, "width", "10%");
+       PAIR_STYLE_INIT(&tag[0], h);
+       print_otag(h, TAG_TD, 1, tag);
+       print_text(h, title);
+       print_stagq(h, tt);
+
+       bufinit(h);
+       bufcat_style(h, "width", "80%");
+       bufcat_style(h, "white-space", "nowrap");
+       bufcat_style(h, "text-align", "center");
+       PAIR_STYLE_INIT(&tag[0], h);
+       print_otag(h, TAG_TD, 1, tag);
+       print_text(h, b);
+       print_stagq(h, tt);
+
+       bufinit(h);
+       bufcat_style(h, "width", "10%");
+       bufcat_style(h, "text-align", "right");
+       PAIR_STYLE_INIT(&tag[0], h);
+       print_otag(h, TAG_TD, 1, tag);
+       print_text(h, title);
+       print_tagq(h, t);
+       return(1);
+}
+
+
+/* ARGSUSED */
+static void
+man_root_post(MAN_ARGS)
+{
+       struct tm        tm;
+       struct htmlpair  tag[2];
+       struct tag      *t, *tt;
+       char             b[BUFSIZ];
+
+       (void)localtime_r(&m->date, &tm);
+
+       if (0 == strftime(b, BUFSIZ - 1, "%B %e, %Y", &tm))
+               err(EXIT_FAILURE, "strftime");
+
+       PAIR_CLASS_INIT(&tag[0], "footer");
+       bufcat_style(h, "width", "100%");
+       PAIR_STYLE_INIT(&tag[1], h);
+       t = print_otag(h, TAG_TABLE, 2, tag);
+       tt = print_otag(h, TAG_TR, 0, NULL);
+
+       bufinit(h);
+       bufcat_style(h, "width", "50%");
+       PAIR_STYLE_INIT(&tag[0], h);
+       print_otag(h, TAG_TD, 1, tag);
+       print_text(h, b);
+       print_stagq(h, tt);
+
+       bufinit(h);
+       bufcat_style(h, "width", "50%");
+       bufcat_style(h, "text-align", "right");
+       PAIR_STYLE_INIT(&tag[0], h);
+       print_otag(h, TAG_TD, 1, tag);
+       if (m->source)
+               print_text(h, m->source);
+       print_tagq(h, t);
+}
+
+
+
+/* ARGSUSED */
+static int
+man_br_pre(MAN_ARGS)
+{
+       struct roffsu    su;
+       struct htmlpair  tag;
+
+       SCALE_VS_INIT(&su, 1);
+
+       if (MAN_sp == n->tok && n->child)
+               a2roffsu(n->child->string, &su, SCALE_VS);
+       else if (MAN_br == n->tok)
+               su.scale = 0;
+
+       bufcat_su(h, "height", &su);
+       PAIR_STYLE_INIT(&tag, h);
+       print_otag(h, TAG_DIV, 1, &tag);
+       return(0);
+}
+
+
+/* ARGSUSED */
+static int
+man_SH_pre(MAN_ARGS)
+{
+       struct htmlpair  tag[2];
+       struct roffsu    su;
+
+       if (MAN_BODY == n->type) {
+               SCALE_HS_INIT(&su, INDENT);
+               bufcat_su(h, "margin-left", &su);
+               PAIR_CLASS_INIT(&tag[0], "sec-body");
+               PAIR_STYLE_INIT(&tag[1], h);
+               print_otag(h, TAG_DIV, 2, tag);
+               return(1);
+       } else if (MAN_BLOCK == n->type) {
+               PAIR_CLASS_INIT(&tag[0], "sec-block");
+               if (n->prev && MAN_SH == n->prev->tok)
+                       if (NULL == n->prev->body->child) {
+                               print_otag(h, TAG_DIV, 1, tag);
+                               return(1);
+                       }
+
+               SCALE_VS_INIT(&su, 1);
+               bufcat_su(h, "margin-top", &su);
+               if (NULL == n->next)
+                       bufcat_su(h, "margin-bottom", &su);
+               PAIR_STYLE_INIT(&tag[1], h);
+               print_otag(h, TAG_DIV, 2, tag);
+               return(1);
+       }
+
+       PAIR_CLASS_INIT(&tag[0], "sec-head");
+       print_otag(h, TAG_DIV, 1, tag);
+       return(1);
+}
+
+
+/* ARGSUSED */
+static int
+man_alt_pre(MAN_ARGS)
+{
+       const struct man_node   *nn;
+       struct tag              *t;
+       int                      i;
+       struct htmlpair          tagi, tagb, *tagp;
+
+       PAIR_CLASS_INIT(&tagi, "italic");
+       PAIR_CLASS_INIT(&tagb, "bold");
+
+       for (i = 0, nn = n->child; nn; nn = nn->next, i++) {
+               switch (n->tok) {
+               case (MAN_BI):
+                       tagp = i % 2 ? &tagi : &tagb;
+                       break;
+               case (MAN_IB):
+                       tagp = i % 2 ? &tagb : &tagi;
+                       break;
+               case (MAN_RI):
+                       tagp = i % 2 ? &tagi : NULL;
+                       break;
+               case (MAN_IR):
+                       tagp = i % 2 ? NULL : &tagi;
+                       break;
+               case (MAN_BR):
+                       tagp = i % 2 ? NULL : &tagb;
+                       break;
+               case (MAN_RB):
+                       tagp = i % 2 ? &tagb : NULL;
+                       break;
+               default:
+                       abort();
+                       /* NOTREACHED */
+               }
+
+               if (i)
+                       h->flags |= HTML_NOSPACE;
+
+               if (tagp) {
+                       t = print_otag(h, TAG_SPAN, 1, tagp);
+                       print_man_node(m, nn, h);
+                       print_tagq(h, t);
+               } else
+                       print_man_node(m, nn, h);
+       }
+
+       return(0);
+}
+
+
+/* ARGSUSED */
+static int
+man_SB_pre(MAN_ARGS)
+{
+       struct htmlpair  tag;
+
+       PAIR_CLASS_INIT(&tag, "small bold");
+       print_otag(h, TAG_SPAN, 1, &tag);
+       return(1);
+}
+
+
+/* ARGSUSED */
+static int
+man_SM_pre(MAN_ARGS)
+{
+       struct htmlpair  tag;
+
+       PAIR_CLASS_INIT(&tag, "small");
+       print_otag(h, TAG_SPAN, 1, &tag);
+       return(1);
+}
+
+
+/* ARGSUSED */
+static int
+man_SS_pre(MAN_ARGS)
+{
+       struct htmlpair  tag[3];
+       struct roffsu    su;
+
+       SCALE_VS_INIT(&su, 1);
+
+       if (MAN_BODY == n->type) {
+               PAIR_CLASS_INIT(&tag[0], "ssec-body");
+               if (n->parent->next && n->child) {
+                       bufcat_su(h, "margin-bottom", &su);
+                       PAIR_STYLE_INIT(&tag[1], h);
+                       print_otag(h, TAG_DIV, 2, tag);
+                       return(1);
+               }
+
+               print_otag(h, TAG_DIV, 1, tag);
+               return(1);
+       } else if (MAN_BLOCK == n->type) {
+               PAIR_CLASS_INIT(&tag[0], "ssec-block");
+               if (n->prev && MAN_SS == n->prev->tok)
+                       if (n->prev->body->child) {
+                               bufcat_su(h, "margin-top", &su);
+                               PAIR_STYLE_INIT(&tag[1], h);
+                               print_otag(h, TAG_DIV, 2, tag);
+                               return(1);
+                       }
+
+               print_otag(h, TAG_DIV, 1, tag);
+               return(1);
+       }
+
+       SCALE_HS_INIT(&su, INDENT - HALFINDENT);
+       bufcat_su(h, "margin-left", &su);
+       PAIR_CLASS_INIT(&tag[0], "ssec-head");
+       PAIR_STYLE_INIT(&tag[1], h);
+       print_otag(h, TAG_DIV, 2, tag);
+       return(1);
+}
+
+
+/* ARGSUSED */
+static int
+man_PP_pre(MAN_ARGS)
+{
+       struct htmlpair  tag;
+       struct roffsu    su;
+       int              i;
+
+       if (MAN_BLOCK != n->type)
+               return(1);
+
+       i = 0;
+
+       if (MAN_ROOT == n->parent->tok) {
+               SCALE_HS_INIT(&su, INDENT);
+               bufcat_su(h, "margin-left", &su);
+               i++;
+       }
+       if (n->next && n->next->child) {
+               SCALE_VS_INIT(&su, 1);
+               bufcat_su(h, "margin-bottom", &su);
+               i++;
+       }
+
+       PAIR_STYLE_INIT(&tag, h);
+       print_otag(h, TAG_DIV, i ? 1 : 0, &tag);
+       return(1);
+}
+
+
+/* ARGSUSED */
+static int
+man_IP_pre(MAN_ARGS)
+{
+       struct roffsu            su;
+       struct htmlpair          tag;
+       const struct man_node   *nn;
+       int                      width;
+
+       /*
+        * This scattering of 1-BU margins and pads is to make sure that
+        * when text overruns its box, the subsequent text isn't flush
+        * up against it.  However, the rest of the right-hand box must
+        * also be adjusted in consideration of this 1-BU space.
+        */
+
+       if (MAN_BODY == n->type) {
+               SCALE_HS_INIT(&su, INDENT);
+               bufcat_su(h, "margin-left", &su);
+               PAIR_STYLE_INIT(&tag, h);
+               print_otag(h, TAG_DIV, 1, &tag);
+               return(1);
+       }
+
+       nn = MAN_BLOCK == n->type ?
+               n->head->child : n->parent->head->child;
+
+       SCALE_HS_INIT(&su, INDENT);
+       width = 0;
+
+       if (MAN_IP == n->tok && NULL != nn)
+               if (NULL != (nn = nn->next)) {
+                       for ( ; nn->next; nn = nn->next)
+                               /* Do nothing. */ ;
+                       width = a2width(nn, &su);
+               }
+
+       if (MAN_TP == n->tok && NULL != nn)
+               width = a2width(nn, &su);
+
+       if (MAN_BLOCK == n->type) {
+               bufcat_su(h, "margin-left", &su);
+               SCALE_VS_INIT(&su, 1);
+               bufcat_su(h, "margin-top", &su);
+               bufcat_style(h, "clear", "both");
+               PAIR_STYLE_INIT(&tag, h);
+               print_otag(h, TAG_DIV, 1, &tag);
+               return(1);
+       }
+
+       bufcat_su(h, "min-width", &su);
+       SCALE_INVERT(&su);
+       bufcat_su(h, "margin-left", &su);
+       SCALE_HS_INIT(&su, 1);
+       bufcat_su(h, "margin-right", &su);
+       bufcat_style(h, "clear", "left");
+
+       if (n->next && n->next->child)
+               bufcat_style(h, "float", "left");
+
+       PAIR_STYLE_INIT(&tag, h);
+       print_otag(h, TAG_DIV, 1, &tag);
+
+       /* With a length string, manually omit the last child. */
+
+       if ( ! width)
+               return(1);
+
+       if (MAN_IP == n->tok)
+               for (nn = n->child; nn->next; nn = nn->next)
+                       print_man_node(m, nn, h);
+       if (MAN_TP == n->tok)
+               for (nn = n->child->next; nn; nn = nn->next)
+                       print_man_node(m, nn, h);
+
+       return(0);
+}
+
+
+/* ARGSUSED */
+static int
+man_HP_pre(MAN_ARGS)
+{
+       const struct man_node   *nn;
+       struct htmlpair          tag;
+       struct roffsu            su;
+
+       if (MAN_HEAD == n->type)
+               return(0);
+
+       nn = MAN_BLOCK == n->type ?
+               n->head->child : n->parent->head->child;
+
+       SCALE_HS_INIT(&su, INDENT);
+
+       if (NULL != nn)
+               (void)a2width(nn, &su);
+
+       if (MAN_BLOCK == n->type) {
+               bufcat_su(h, "margin-left", &su);
+               SCALE_VS_INIT(&su, 1);
+               bufcat_su(h, "margin-top", &su);
+               bufcat_style(h, "clear", "both");
+               PAIR_STYLE_INIT(&tag, h);
+               print_otag(h, TAG_DIV, 1, &tag);
+               return(1);
+       }
+
+       bufcat_su(h, "margin-left", &su);
+       SCALE_INVERT(&su);
+       bufcat_su(h, "text-indent", &su);
+
+       PAIR_STYLE_INIT(&tag, h);
+       print_otag(h, TAG_DIV, 1, &tag);
+       return(1);
+}
+
+
+/* ARGSUSED */
+static int
+man_B_pre(MAN_ARGS)
+{
+       struct htmlpair  tag;
+
+       PAIR_CLASS_INIT(&tag, "bold");
+       print_otag(h, TAG_SPAN, 1, &tag);
+       return(1);
+}
+
+
+/* ARGSUSED */
+static int
+man_I_pre(MAN_ARGS)
+{
+       struct htmlpair  tag;
+
+       PAIR_CLASS_INIT(&tag, "italic");
+       print_otag(h, TAG_SPAN, 1, &tag);
+       return(1);
+}
+
+
+/* ARGSUSED */
+static int
+man_ign_pre(MAN_ARGS)
+{
+
+       return(0);
+}
+
+
+/* ARGSUSED */
+static int
+man_RS_pre(MAN_ARGS)
+{
+       struct htmlpair  tag;
+       struct roffsu    su;
+
+       if (MAN_HEAD == n->type)
+               return(0);
+       else if (MAN_BODY == n->type)
+               return(1);
+
+       SCALE_HS_INIT(&su, INDENT);
+       bufcat_su(h, "margin-left", &su);
+
+       if (n->head->child) {
+               SCALE_VS_INIT(&su, 1);
+               a2width(n->head->child, &su);
+               bufcat_su(h, "margin-top", &su);
+       }
+
+       PAIR_STYLE_INIT(&tag, h);
+       print_otag(h, TAG_DIV, 1, &tag);
+       return(1);
+}
diff --git a/usr.bin/mandoc/man_macro.c b/usr.bin/mandoc/man_macro.c
new file mode 100644 (file)
index 0000000..6a62e78
--- /dev/null
@@ -0,0 +1,374 @@
+/*     $Id: man_macro.c,v 1.8 2009/09/18 22:46:14 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009 Kristaps Dzonsons <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 <assert.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "libman.h"
+
+#define        REW_REWIND      (0)             /* See rew_scope(). */
+#define        REW_NOHALT      (1)             /* See rew_scope(). */
+#define        REW_HALT        (2)             /* See rew_scope(). */
+
+static int              in_line_eoln(MACRO_PROT_ARGS);
+static int              blk_imp(MACRO_PROT_ARGS);
+static int              blk_close(MACRO_PROT_ARGS);
+
+static int              rew_scope(enum man_type, struct man *, int);
+static int              rew_dohalt(int, enum man_type,
+                               const struct man_node *);
+static int              rew_block(int, enum man_type,
+                               const struct man_node *);
+
+const  struct man_macro __man_macros[MAN_MAX] = {
+       { in_line_eoln, 0 }, /* br */
+       { in_line_eoln, 0 }, /* TH */
+       { blk_imp, MAN_SCOPED }, /* SH */
+       { blk_imp, MAN_SCOPED }, /* SS */
+       { blk_imp, MAN_SCOPED | MAN_FSCOPED }, /* TP */
+       { blk_imp, 0 }, /* LP */
+       { blk_imp, 0 }, /* PP */
+       { blk_imp, 0 }, /* P */
+       { blk_imp, 0 }, /* IP */
+       { blk_imp, 0 }, /* HP */
+       { in_line_eoln, MAN_SCOPED }, /* SM */
+       { in_line_eoln, MAN_SCOPED }, /* SB */
+       { in_line_eoln, 0 }, /* BI */
+       { in_line_eoln, 0 }, /* IB */
+       { in_line_eoln, 0 }, /* BR */
+       { in_line_eoln, 0 }, /* RB */
+       { in_line_eoln, MAN_SCOPED }, /* R */
+       { in_line_eoln, MAN_SCOPED }, /* B */
+       { in_line_eoln, MAN_SCOPED }, /* I */
+       { in_line_eoln, 0 }, /* IR */
+       { in_line_eoln, 0 }, /* RI */
+       { in_line_eoln, 0 }, /* na */
+       { in_line_eoln, 0 }, /* i */
+       { in_line_eoln, 0 }, /* sp */
+       { in_line_eoln, 0 }, /* nf */
+       { in_line_eoln, 0 }, /* fi */
+       { in_line_eoln, 0 }, /* r */
+       { blk_close, 0 }, /* RE */
+       { blk_imp, MAN_EXPLICIT }, /* RS */
+       { in_line_eoln, 0 }, /* DT */
+       { in_line_eoln, 0 }, /* UC */
+};
+
+const  struct man_macro * const man_macros = __man_macros;
+
+
+int
+man_unscope(struct man *m, const struct man_node *n)
+{
+
+       assert(n);
+       m->next = MAN_NEXT_SIBLING;
+
+       /* LINTED */
+       while (m->last != n) {
+               if ( ! man_valid_post(m))
+                       return(0);
+               if ( ! man_action_post(m))
+                       return(0);
+               m->last = m->last->parent;
+               assert(m->last);
+       }
+
+       if ( ! man_valid_post(m))
+               return(0);
+       return(man_action_post(m));
+}
+
+
+static int
+rew_block(int ntok, enum man_type type, const struct man_node *n)
+{
+
+       if (MAN_BLOCK == type && ntok == n->parent->tok &&
+                       MAN_BODY == n->parent->type)
+               return(REW_REWIND);
+       return(ntok == n->tok ? REW_HALT : REW_NOHALT);
+}
+
+
+/*
+ * There are three scope levels: scoped to the root (all), scoped to the
+ * section (all less sections), and scoped to subsections (all less
+ * sections and subsections).
+ */
+static int
+rew_dohalt(int tok, enum man_type type, const struct man_node *n)
+{
+       int              c;
+
+       if (MAN_ROOT == n->type)
+               return(REW_HALT);
+       assert(n->parent);
+       if (MAN_ROOT == n->parent->type)
+               return(REW_REWIND);
+       if (MAN_VALID & n->flags)
+               return(REW_NOHALT);
+
+       /* Rewind to ourselves, first. */
+       if (type == n->type && tok == n->tok)
+               return(REW_REWIND);
+
+       switch (tok) {
+       case (MAN_SH):
+               break;
+       case (MAN_SS):
+               /* Rewind to a section, if a block. */
+               if (REW_NOHALT != (c = rew_block(MAN_SH, type, n)))
+                       return(c);
+               break;
+       case (MAN_RS):
+               /* Rewind to a subsection, if a block. */
+               if (REW_NOHALT != (c = rew_block(MAN_SS, type, n)))
+                       return(c);
+               /* Rewind to a section, if a block. */
+               if (REW_NOHALT != (c = rew_block(MAN_SH, type, n)))
+                       return(c);
+               break;
+       default:
+               /* Rewind to an offsetter, if a block. */
+               if (REW_NOHALT != (c = rew_block(MAN_RS, type, n)))
+                       return(c);
+               /* Rewind to a subsection, if a block. */
+               if (REW_NOHALT != (c = rew_block(MAN_SS, type, n)))
+                       return(c);
+               /* Rewind to a section, if a block. */
+               if (REW_NOHALT != (c = rew_block(MAN_SH, type, n)))
+                       return(c);
+               break;
+       }
+
+       return(REW_NOHALT);
+}
+
+
+/*
+ * Rewinding entails ascending the parse tree until a coherent point,
+ * for example, the `SH' macro will close out any intervening `SS'
+ * scopes.  When a scope is closed, it must be validated and actioned.
+ */
+static int
+rew_scope(enum man_type type, struct man *m, int tok)
+{
+       struct man_node *n;
+       int              c;
+
+       /* LINTED */
+       for (n = m->last; n; n = n->parent) {
+               /*
+                * Whether we should stop immediately (REW_HALT), stop
+                * and rewind until this point (REW_REWIND), or keep
+                * rewinding (REW_NOHALT).
+                */
+               c = rew_dohalt(tok, type, n);
+               if (REW_HALT == c)
+                       return(1);
+               if (REW_REWIND == c)
+                       break;
+       }
+
+       /* Rewind until the current point. */
+
+       assert(n);
+       return(man_unscope(m, n));
+}
+
+
+/* ARGSUSED */
+int
+blk_close(MACRO_PROT_ARGS)
+{
+       int                      ntok;
+       const struct man_node   *nn;
+
+       switch (tok) {
+       case (MAN_RE):
+               ntok = MAN_RS;
+               break;
+       default:
+               abort();
+               /* NOTREACHED */
+       }
+
+       for (nn = m->last->parent; nn; nn = nn->parent)
+               if (ntok == nn->tok)
+                       break;
+
+       if (NULL == nn)
+               if ( ! man_pwarn(m, line, ppos, WNOSCOPE))
+                       return(0);
+
+       if ( ! rew_scope(MAN_BODY, m, ntok))
+               return(0);
+       if ( ! rew_scope(MAN_BLOCK, m, ntok))
+               return(0);
+       m->next = MAN_NEXT_SIBLING;
+       return(1);
+}
+
+
+/*
+ * Parse an implicit-block macro.  These contain a MAN_HEAD and a
+ * MAN_BODY contained within a MAN_BLOCK.  Rules for closing out other
+ * scopes, such as `SH' closing out an `SS', are defined in the rew
+ * routines.
+ */
+int
+blk_imp(MACRO_PROT_ARGS)
+{
+       int              w, la;
+       char            *p;
+       struct man_node *n;
+
+       /* Close out prior scopes. */
+
+       if ( ! rew_scope(MAN_BODY, m, tok))
+               return(0);
+       if ( ! rew_scope(MAN_BLOCK, m, tok))
+               return(0);
+
+       /* Allocate new block & head scope. */
+
+       if ( ! man_block_alloc(m, line, ppos, tok))
+               return(0);
+       if ( ! man_head_alloc(m, line, ppos, tok))
+               return(0);
+
+       n = m->last;
+
+       /* Add line arguments. */
+
+       for (;;) {
+               la = *pos;
+               w = man_args(m, line, pos, buf, &p);
+
+               if (-1 == w)
+                       return(0);
+               if (0 == w)
+                       break;
+
+               if ( ! man_word_alloc(m, line, la, p))
+                       return(0);
+       }
+
+       /* Close out head and open body (unless MAN_SCOPE). */
+
+       if (MAN_SCOPED & man_macros[tok].flags) {
+               /* If we're forcing scope (`TP'), keep it open. */
+               if (MAN_FSCOPED & man_macros[tok].flags) {
+                       m->flags |= MAN_BLINE;
+                       return(1);
+               } else if (n == m->last) {
+                       m->flags |= MAN_BLINE;
+                       return(1);
+               }
+       }
+
+       if ( ! rew_scope(MAN_HEAD, m, tok))
+               return(0);
+
+       return(man_body_alloc(m, line, ppos, tok));
+}
+
+
+int
+in_line_eoln(MACRO_PROT_ARGS)
+{
+       int              w, la;
+       char            *p;
+       struct man_node *n;
+
+       if ( ! man_elem_alloc(m, line, ppos, tok))
+               return(0);
+
+       n = m->last;
+
+       for (;;) {
+               la = *pos;
+               w = man_args(m, line, pos, buf, &p);
+
+               if (-1 == w)
+                       return(0);
+               if (0 == w)
+                       break;
+
+               if ( ! man_word_alloc(m, line, la, p))
+                       return(0);
+       }
+
+       if (n == m->last && MAN_SCOPED & man_macros[tok].flags) {
+               m->flags |= MAN_ELINE;
+               return(1);
+       }
+
+       /*
+        * Note that when TH is pruned, we'll be back at the root, so
+        * make sure that we don't clobber as its sibling.
+        */
+
+       for ( ; m->last; m->last = m->last->parent) {
+               if (m->last == n)
+                       break;
+               if (m->last->type == MAN_ROOT)
+                       break;
+               if ( ! man_valid_post(m))
+                       return(0);
+               if ( ! man_action_post(m))
+                       return(0);
+       }
+
+       assert(m->last);
+
+       /*
+        * Same here regarding whether we're back at the root.
+        */
+
+       if (m->last->type != MAN_ROOT && ! man_valid_post(m))
+               return(0);
+       if (m->last->type != MAN_ROOT && ! man_action_post(m))
+               return(0);
+       if (m->last->type != MAN_ROOT)
+               m->next = MAN_NEXT_SIBLING;
+
+       return(1);
+}
+
+
+int
+man_macroend(struct man *m)
+{
+       struct man_node *n;
+
+       n = MAN_VALID & m->last->flags ?
+               m->last->parent : m->last;
+
+       for ( ; n; n = n->parent) {
+               if (MAN_BLOCK != n->type)
+                       continue;
+               if ( ! (MAN_EXPLICIT & man_macros[n->tok].flags))
+                       continue;
+               if ( ! man_nwarn(m, n, WEXITSCOPE))
+                       return(0);
+       }
+
+       return(man_unscope(m, m->first));
+}
diff --git a/usr.bin/mandoc/man_term.c b/usr.bin/mandoc/man_term.c
new file mode 100644 (file)
index 0000000..f6d59cd
--- /dev/null
@@ -0,0 +1,986 @@
+/*     $Id: man_term.c,v 1.18 2009/10/21 19:13:50 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009 Kristaps Dzonsons <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 <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "out.h"
+#include "man.h"
+#include "term.h"
+#include "chars.h"
+#include "main.h"
+
+#define        INDENT            7
+#define        HALFINDENT        3
+
+struct mtermp {
+       int               fl;
+#define        MANT_LITERAL     (1 << 0)
+       /*
+        * Default amount to indent the left margin after leading text
+        * has been printed (e.g., `HP' left-indent, `TP' and `IP' body
+        * indent).  This needs to be saved because `HP' and so on, if
+        * not having a specified value, must default.
+        *
+        * Note that this is the indentation AFTER the left offset, so
+        * the total offset is usually offset + lmargin.
+        */
+       size_t            lmargin;
+       /*
+        * The default offset, i.e., the amount between any text and the
+        * page boundary.
+        */
+       size_t            offset;
+};
+
+#define        DECL_ARGS         struct termp *p, \
+                         struct mtermp *mt, \
+                         const struct man_node *n, \
+                         const struct man_meta *m
+
+struct termact {
+       int             (*pre)(DECL_ARGS);
+       void            (*post)(DECL_ARGS);
+};
+
+static int               arg2width(const struct man_node *);
+static int               arg2height(const struct man_node *);
+
+static void              print_head(struct termp *,
+                               const struct man_meta *);
+static void              print_body(DECL_ARGS);
+static void              print_node(DECL_ARGS);
+static void              print_foot(struct termp *,
+                               const struct man_meta *);
+static void              print_bvspace(struct termp *,
+                               const struct man_node *);
+
+static int               pre_B(DECL_ARGS);
+static int               pre_BI(DECL_ARGS);
+static int               pre_HP(DECL_ARGS);
+static int               pre_I(DECL_ARGS);
+static int               pre_IP(DECL_ARGS);
+static int               pre_IR(DECL_ARGS);
+static int               pre_PP(DECL_ARGS);
+static int               pre_RB(DECL_ARGS);
+static int               pre_RI(DECL_ARGS);
+static int               pre_RS(DECL_ARGS);
+static int               pre_SH(DECL_ARGS);
+static int               pre_SS(DECL_ARGS);
+static int               pre_TP(DECL_ARGS);
+static int               pre_br(DECL_ARGS);
+static int               pre_fi(DECL_ARGS);
+static int               pre_ign(DECL_ARGS);
+static int               pre_nf(DECL_ARGS);
+static int               pre_r(DECL_ARGS);
+static int               pre_sp(DECL_ARGS);
+
+static void              post_B(DECL_ARGS);
+static void              post_I(DECL_ARGS);
+static void              post_IP(DECL_ARGS);
+static void              post_HP(DECL_ARGS);
+static void              post_RS(DECL_ARGS);
+static void              post_SH(DECL_ARGS);
+static void              post_SS(DECL_ARGS);
+static void              post_TP(DECL_ARGS);
+static void              post_i(DECL_ARGS);
+
+static const struct termact termacts[MAN_MAX] = {
+       { pre_br, NULL }, /* br */
+       { NULL, NULL }, /* TH */
+       { pre_SH, post_SH }, /* SH */
+       { pre_SS, post_SS }, /* SS */
+       { pre_TP, post_TP }, /* TP */
+       { pre_PP, NULL }, /* LP */
+       { pre_PP, NULL }, /* PP */
+       { pre_PP, NULL }, /* P */
+       { pre_IP, post_IP }, /* IP */
+       { pre_HP, post_HP }, /* HP */
+       { NULL, NULL }, /* SM */
+       { pre_B, post_B }, /* SB */
+       { pre_BI, NULL }, /* BI */
+       { pre_BI, NULL }, /* IB */
+       { pre_RB, NULL }, /* BR */
+       { pre_RB, NULL }, /* RB */
+       { NULL, NULL }, /* R */
+       { pre_B, post_B }, /* B */
+       { pre_I, post_I }, /* I */
+       { pre_IR, NULL }, /* IR */
+       { pre_RI, NULL }, /* RI */
+       { NULL, NULL }, /* na */
+       { pre_I, post_i }, /* i */
+       { pre_sp, NULL }, /* sp */
+       { pre_nf, NULL }, /* nf */
+       { pre_fi, NULL }, /* fi */
+       { pre_r, NULL }, /* r */
+       { NULL, NULL }, /* RE */
+       { pre_RS, post_RS }, /* RS */
+       { pre_ign, NULL }, /* DT */
+       { pre_ign, NULL }, /* UC */
+};
+
+
+
+void
+terminal_man(void *arg, const struct man *man)
+{
+       struct termp            *p;
+       const struct man_node   *n;
+       const struct man_meta   *m;
+       struct mtermp            mt;
+
+       p = (struct termp *)arg;
+
+       if (NULL == p->symtab)
+               switch (p->enc) {
+               case (TERMENC_ASCII):
+                       p->symtab = chars_init(CHARS_ASCII);
+                       break;
+               default:
+                       abort();
+                       /* NOTREACHED */
+               }
+
+       n = man_node(man);
+       m = man_meta(man);
+
+       print_head(p, m);
+       p->flags |= TERMP_NOSPACE;
+
+       mt.fl = 0;
+       mt.lmargin = INDENT;
+       mt.offset = INDENT;
+
+       if (n->child)
+               print_body(p, &mt, n->child, m);
+       print_foot(p, m);
+}
+
+
+static int
+arg2height(const struct man_node *n)
+{
+       struct roffsu    su;
+
+       assert(MAN_TEXT == n->type);
+       assert(n->string);
+       if ( ! a2roffsu(n->string, &su, SCALE_VS))
+               SCALE_VS_INIT(&su, strlen(n->string));
+
+       return((int)term_vspan(&su));
+}
+
+
+static int
+arg2width(const struct man_node *n)
+{
+       struct roffsu    su;
+
+       assert(MAN_TEXT == n->type);
+       assert(n->string);
+       if ( ! a2roffsu(n->string, &su, SCALE_BU))
+               return(-1);
+
+       return((int)term_hspan(&su));
+}
+
+
+static void
+print_bvspace(struct termp *p, const struct man_node *n)
+{
+       term_newln(p);
+
+       if (NULL == n->prev)
+               return;
+
+       if (MAN_SS == n->prev->tok)
+               return;
+       if (MAN_SH == n->prev->tok)
+               return;
+
+       term_vspace(p);
+}
+
+
+/* ARGSUSED */
+static int
+pre_ign(DECL_ARGS)
+{
+
+       return(0);
+}
+
+
+/* ARGSUSED */
+static int
+pre_I(DECL_ARGS)
+{
+
+       p->under++;
+       return(1);
+}
+
+
+/* ARGSUSED */
+static int
+pre_r(DECL_ARGS)
+{
+
+       p->bold = p->under = 0;
+       return(1);
+}
+
+
+/* ARGSUSED */
+static void
+post_i(DECL_ARGS)
+{
+
+       if (n->nchild)
+               p->under--;
+}
+
+
+/* ARGSUSED */
+static void
+post_I(DECL_ARGS)
+{
+
+       p->under--;
+}
+
+
+/* ARGSUSED */
+static int
+pre_fi(DECL_ARGS)
+{
+
+       mt->fl &= ~MANT_LITERAL;
+       return(1);
+}
+
+
+/* ARGSUSED */
+static int
+pre_nf(DECL_ARGS)
+{
+
+       term_newln(p);
+       mt->fl |= MANT_LITERAL;
+       return(1);
+}
+
+
+/* ARGSUSED */
+static int
+pre_IR(DECL_ARGS)
+{
+       const struct man_node *nn;
+       int              i;
+
+       for (i = 0, nn = n->child; nn; nn = nn->next, i++) {
+               if ( ! (i % 2))
+                       p->under++;
+               if (i > 0)
+                       p->flags |= TERMP_NOSPACE;
+               print_node(p, mt, nn, m);
+               if ( ! (i % 2))
+                       p->under--;
+       }
+       return(0);
+}
+
+
+/* ARGSUSED */
+static int
+pre_RB(DECL_ARGS)
+{
+       const struct man_node *nn;
+       int              i;
+
+       for (i = 0, nn = n->child; nn; nn = nn->next, i++) {
+               if (i % 2 && MAN_RB == n->tok)
+                       p->bold++;
+               else if ( ! (i % 2) && MAN_RB != n->tok)
+                       p->bold++;
+
+               if (i > 0)
+                       p->flags |= TERMP_NOSPACE;
+
+               print_node(p, mt, nn, m);
+
+               if (i % 2 && MAN_RB == n->tok)
+                       p->bold--;
+               else if ( ! (i % 2) && MAN_RB != n->tok)
+                       p->bold--;
+       }
+       return(0);
+}
+
+
+/* ARGSUSED */
+static int
+pre_RI(DECL_ARGS)
+{
+       const struct man_node *nn;
+       int              i;
+
+       for (i = 0, nn = n->child; nn; nn = nn->next, i++) {
+               if ( ! (i % 2))
+                       p->under++;
+               if (i > 0)
+                       p->flags |= TERMP_NOSPACE;
+               print_node(p, mt, nn, m);
+               if ( ! (i % 2))
+                       p->under--;
+       }
+       return(0);
+}
+
+
+/* ARGSUSED */
+static int
+pre_BI(DECL_ARGS)
+{
+       const struct man_node   *nn;
+       int                      i;
+
+       for (i = 0, nn = n->child; nn; nn = nn->next, i++) {
+               if (i % 2 && MAN_BI == n->tok)
+                       p->under++;
+               else if (i % 2)
+                       p->bold++;
+               else if (MAN_BI == n->tok)
+                       p->bold++;
+               else
+                       p->under++;
+
+               if (i)
+                       p->flags |= TERMP_NOSPACE;
+               print_node(p, mt, nn, m);
+
+               if (i % 2 && MAN_BI == n->tok)
+                       p->under--;
+               else if (i % 2)
+                       p->bold--;
+               else if (MAN_BI == n->tok)
+                       p->bold--;
+               else
+                       p->under--;
+       }
+       return(0);
+}
+
+
+/* ARGSUSED */
+static int
+pre_B(DECL_ARGS)
+{
+
+       p->bold++;
+       return(1);
+}
+
+
+/* ARGSUSED */
+static void
+post_B(DECL_ARGS)
+{
+
+       p->bold--;
+}
+
+
+/* ARGSUSED */
+static int
+pre_sp(DECL_ARGS)
+{
+       int              i, len;
+
+       len = n->child ? arg2height(n->child) : 1;
+
+       if (0 == len)
+               term_newln(p);
+       for (i = 0; i < len; i++)
+               term_vspace(p);
+
+       return(0);
+}
+
+
+/* ARGSUSED */
+static int
+pre_br(DECL_ARGS)
+{
+
+       term_newln(p);
+       return(0);
+}
+
+
+/* ARGSUSED */
+static int
+pre_HP(DECL_ARGS)
+{
+       size_t                   len;
+       int                      ival;
+       const struct man_node   *nn;
+
+       switch (n->type) {
+       case (MAN_BLOCK):
+               print_bvspace(p, n);
+               return(1);
+       case (MAN_BODY):
+               p->flags |= TERMP_NOBREAK;
+               p->flags |= TERMP_TWOSPACE;
+               break;
+       default:
+               return(0);
+       }
+
+       len = mt->lmargin;
+       ival = -1;
+
+       /* Calculate offset. */
+
+       if (NULL != (nn = n->parent->head->child))
+               if ((ival = arg2width(nn)) >= 0)
+                       len = (size_t)ival;
+
+       if (0 == len)
+               len = 1;
+
+       p->offset = mt->offset;
+       p->rmargin = mt->offset + len;
+
+       if (ival >= 0)
+               mt->lmargin = (size_t)ival;
+
+       return(1);
+}
+
+
+/* ARGSUSED */
+static void
+post_HP(DECL_ARGS)
+{
+
+       switch (n->type) {
+       case (MAN_BLOCK):
+               term_flushln(p);
+               break;
+       case (MAN_BODY):
+               term_flushln(p);
+               p->flags &= ~TERMP_NOBREAK;
+               p->flags &= ~TERMP_TWOSPACE;
+               p->offset = mt->offset;
+               p->rmargin = p->maxrmargin;
+               break;
+       default:
+               break;
+       }
+}
+
+
+/* ARGSUSED */
+static int
+pre_PP(DECL_ARGS)
+{
+
+       switch (n->type) {
+       case (MAN_BLOCK):
+               mt->lmargin = INDENT;
+               print_bvspace(p, n);
+               break;
+       default:
+               p->offset = mt->offset;
+               break;
+       }
+
+       return(1);
+}
+
+
+/* ARGSUSED */
+static int
+pre_IP(DECL_ARGS)
+{
+       const struct man_node   *nn;
+       size_t                   len;
+       int                      ival;
+
+       switch (n->type) {
+       case (MAN_BODY):
+               p->flags |= TERMP_NOLPAD;
+               p->flags |= TERMP_NOSPACE;
+               break;
+       case (MAN_HEAD):
+               p->flags |= TERMP_NOBREAK;
+               p->flags |= TERMP_TWOSPACE;
+               break;
+       case (MAN_BLOCK):
+               print_bvspace(p, n);
+               /* FALLTHROUGH */
+       default:
+               return(1);
+       }
+
+       len = mt->lmargin;
+       ival = -1;
+
+       /* Calculate offset. */
+
+       if (NULL != (nn = n->parent->head->child))
+               if (NULL != (nn = nn->next)) {
+                       for ( ; nn->next; nn = nn->next)
+                               /* Do nothing. */ ;
+                       if ((ival = arg2width(nn)) >= 0)
+                               len = (size_t)ival;
+               }
+
+       switch (n->type) {
+       case (MAN_HEAD):
+               /* Handle zero-width lengths. */
+               if (0 == len)
+                       len = 1;
+
+               p->offset = mt->offset;
+               p->rmargin = mt->offset + len;
+               if (ival < 0)
+                       break;
+
+               /* Set the saved left-margin. */
+               mt->lmargin = (size_t)ival;
+
+               /* Don't print the length value. */
+               for (nn = n->child; nn->next; nn = nn->next)
+                       print_node(p, mt, nn, m);
+               return(0);
+       case (MAN_BODY):
+               p->offset = mt->offset + len;
+               p->rmargin = p->maxrmargin;
+               break;
+       default:
+               break;
+       }
+
+       return(1);
+}
+
+
+/* ARGSUSED */
+static void
+post_IP(DECL_ARGS)
+{
+
+       switch (n->type) {
+       case (MAN_HEAD):
+               term_flushln(p);
+               p->flags &= ~TERMP_NOBREAK;
+               p->flags &= ~TERMP_TWOSPACE;
+               p->rmargin = p->maxrmargin;
+               break;
+       case (MAN_BODY):
+               term_flushln(p);
+               p->flags &= ~TERMP_NOLPAD;
+               break;
+       default:
+               break;
+       }
+}
+
+
+/* ARGSUSED */
+static int
+pre_TP(DECL_ARGS)
+{
+       const struct man_node   *nn;
+       size_t                   len;
+       int                      ival;
+
+       switch (n->type) {
+       case (MAN_HEAD):
+               p->flags |= TERMP_NOBREAK;
+               p->flags |= TERMP_TWOSPACE;
+               break;
+       case (MAN_BODY):
+               p->flags |= TERMP_NOLPAD;
+               p->flags |= TERMP_NOSPACE;
+               break;
+       case (MAN_BLOCK):
+               print_bvspace(p, n);
+               /* FALLTHROUGH */
+       default:
+               return(1);
+       }
+
+       len = (size_t)mt->lmargin;
+       ival = -1;
+
+       /* Calculate offset. */
+
+       if (NULL != (nn = n->parent->head->child))
+               if (NULL != nn->next)
+                       if ((ival = arg2width(nn)) >= 0)
+                               len = (size_t)ival;
+
+       switch (n->type) {
+       case (MAN_HEAD):
+               /* Handle zero-length properly. */
+               if (0 == len)
+                       len = 1;
+
+               p->offset = mt->offset;
+               p->rmargin = mt->offset + len;
+
+               /* Don't print same-line elements. */
+               for (nn = n->child; nn; nn = nn->next)
+                       if (nn->line > n->line)
+                               print_node(p, mt, nn, m);
+
+               if (ival >= 0)
+                       mt->lmargin = (size_t)ival;
+
+               return(0);
+       case (MAN_BODY):
+               p->offset = mt->offset + len;
+               p->rmargin = p->maxrmargin;
+               break;
+       default:
+               break;
+       }
+
+       return(1);
+}
+
+
+/* ARGSUSED */
+static void
+post_TP(DECL_ARGS)
+{
+
+       switch (n->type) {
+       case (MAN_HEAD):
+               term_flushln(p);
+               p->flags &= ~TERMP_NOBREAK;
+               p->flags &= ~TERMP_TWOSPACE;
+               p->rmargin = p->maxrmargin;
+               break;
+       case (MAN_BODY):
+               term_flushln(p);
+               p->flags &= ~TERMP_NOLPAD;
+               break;
+       default:
+               break;
+       }
+}
+
+
+/* ARGSUSED */
+static int
+pre_SS(DECL_ARGS)
+{
+
+       switch (n->type) {
+       case (MAN_BLOCK):
+               mt->lmargin = INDENT;
+               mt->offset = INDENT;
+               /* If following a prior empty `SS', no vspace. */
+               if (n->prev && MAN_SS == n->prev->tok)
+                       if (NULL == n->prev->body->child)
+                               break;
+               if (NULL == n->prev)
+                       break;
+               term_vspace(p);
+               break;
+       case (MAN_HEAD):
+               p->bold++;
+               p->offset = HALFINDENT;
+               break;
+       case (MAN_BODY):
+               p->offset = mt->offset;
+               break;
+       default:
+               break;
+       }
+
+       return(1);
+}
+
+
+/* ARGSUSED */
+static void
+post_SS(DECL_ARGS)
+{
+
+       switch (n->type) {
+       case (MAN_HEAD):
+               term_newln(p);
+               p->bold--;
+               break;
+       case (MAN_BODY):
+               term_newln(p);
+               break;
+       default:
+               break;
+       }
+}
+
+
+/* ARGSUSED */
+static int
+pre_SH(DECL_ARGS)
+{
+
+       switch (n->type) {
+       case (MAN_BLOCK):
+               mt->lmargin = INDENT;
+               mt->offset = INDENT;
+               /* If following a prior empty `SH', no vspace. */
+               if (n->prev && MAN_SH == n->prev->tok)
+                       if (NULL == n->prev->body->child)
+                               break;
+               term_vspace(p);
+               break;
+       case (MAN_HEAD):
+               p->bold++;
+               p->offset = 0;
+               break;
+       case (MAN_BODY):
+               p->offset = mt->offset;
+               break;
+       default:
+               break;
+       }
+
+       return(1);
+}
+
+
+/* ARGSUSED */
+static void
+post_SH(DECL_ARGS)
+{
+
+       switch (n->type) {
+       case (MAN_HEAD):
+               term_newln(p);
+               p->bold--;
+               break;
+       case (MAN_BODY):
+               term_newln(p);
+               break;
+       default:
+               break;
+       }
+}
+
+
+/* ARGSUSED */
+static int
+pre_RS(DECL_ARGS)
+{
+       const struct man_node   *nn;
+       int                      ival;
+
+       switch (n->type) {
+       case (MAN_BLOCK):
+               term_newln(p);
+               return(1);
+       case (MAN_HEAD):
+               return(0);
+       default:
+               break;
+       }
+
+       if (NULL == (nn = n->parent->head->child)) {
+               mt->offset = mt->lmargin + INDENT;
+               p->offset = mt->offset;
+               return(1);
+       }
+
+       if ((ival = arg2width(nn)) < 0)
+               return(1);
+
+       mt->offset = INDENT + (size_t)ival;
+       p->offset = mt->offset;
+
+       return(1);
+}
+
+
+/* ARGSUSED */
+static void
+post_RS(DECL_ARGS)
+{
+
+       switch (n->type) {
+       case (MAN_BLOCK):
+               mt->offset = mt->lmargin = INDENT;
+               break;
+       default:
+               term_newln(p);
+               p->offset = INDENT;
+               break;
+       }
+}
+
+
+static void
+print_node(DECL_ARGS)
+{
+       int              c, sz;
+
+       c = 1;
+
+       switch (n->type) {
+       case(MAN_TEXT):
+               if (0 == *n->string) {
+                       term_vspace(p);
+                       break;
+               }
+               /*
+                * Note!  This is hacky.  Here, we recognise the `\c'
+                * escape embedded in so many -man pages.  It's supposed
+                * to remove the subsequent space, so we mark NOSPACE if
+                * it's encountered in the string.
+                */
+               sz = (int)strlen(n->string);
+               term_word(p, n->string);
+               if (sz >= 2 && n->string[sz - 1] == 'c' &&
+                               n->string[sz - 2] == '\\')
+                       p->flags |= TERMP_NOSPACE;
+               /* FIXME: this means that macro lines are munged!  */
+               if (MANT_LITERAL & mt->fl) {
+                       p->flags |= TERMP_NOSPACE;
+                       term_flushln(p);
+               }
+               break;
+       default:
+               if (termacts[n->tok].pre)
+                       c = (*termacts[n->tok].pre)(p, mt, n, m);
+               break;
+       }
+
+       if (c && n->child)
+               print_body(p, mt, n->child, m);
+
+       if (MAN_TEXT != n->type)
+               if (termacts[n->tok].post)
+                       (*termacts[n->tok].post)(p, mt, n, m);
+}
+
+
+static void
+print_body(DECL_ARGS)
+{
+
+       print_node(p, mt, n, m);
+       if ( ! n->next)
+               return;
+       print_body(p, mt, n->next, m);
+}
+
+
+static void
+print_foot(struct termp *p, const struct man_meta *meta)
+{
+       struct tm       *tm;
+       char             buf[BUFSIZ];
+
+       tm = localtime(&meta->date);
+
+       if (0 == strftime(buf, p->rmargin, "%B %d, %Y", tm))
+               (void)strlcpy(buf, "(invalid date)", BUFSIZ);
+
+       term_vspace(p);
+
+       p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
+       p->rmargin = p->maxrmargin - strlen(buf);
+       p->offset = 0;
+
+       if (meta->source)
+               term_word(p, meta->source);
+       if (meta->source)
+               term_word(p, "");
+       term_flushln(p);
+
+       p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
+       p->offset = p->rmargin;
+       p->rmargin = p->maxrmargin;
+       p->flags &= ~TERMP_NOBREAK;
+
+       term_word(p, buf);
+       term_flushln(p);
+}
+
+
+static void
+print_head(struct termp *p, const struct man_meta *meta)
+{
+       char            *buf, *title;
+
+       p->rmargin = p->maxrmargin;
+       p->offset = 0;
+
+       if (NULL == (buf = malloc(p->rmargin)))
+               err(EXIT_FAILURE, "malloc");
+       if (NULL == (title = malloc(p->rmargin)))
+               err(EXIT_FAILURE, "malloc");
+
+       if (meta->vol)
+               (void)strlcpy(buf, meta->vol, p->rmargin);
+       else
+               *buf = 0;
+
+       (void)snprintf(title, p->rmargin, "%s(%d)",
+                       meta->title, meta->msec);
+
+       p->offset = 0;
+       p->rmargin = (p->maxrmargin - strlen(buf) + 1) / 2;
+       p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
+
+       term_word(p, title);
+       term_flushln(p);
+
+       p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
+       p->offset = p->rmargin;
+       p->rmargin = p->maxrmargin - strlen(title);
+
+       term_word(p, buf);
+       term_flushln(p);
+
+       p->offset = p->rmargin;
+       p->rmargin = p->maxrmargin;
+       p->flags &= ~TERMP_NOBREAK;
+       p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
+
+       term_word(p, title);
+       term_flushln(p);
+
+       p->rmargin = p->maxrmargin;
+       p->offset = 0;
+       p->flags &= ~TERMP_NOSPACE;
+
+       free(title);
+       free(buf);
+}
diff --git a/usr.bin/mandoc/man_validate.c b/usr.bin/mandoc/man_validate.c
new file mode 100644 (file)
index 0000000..38a7283
--- /dev/null
@@ -0,0 +1,280 @@
+/*     $Id: man_validate.c,v 1.10 2009/10/21 19:13:50 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009 Kristaps Dzonsons <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 <errno.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdlib.h>
+
+#include "libman.h"
+#include "libmandoc.h"
+
+#define        CHKARGS   struct man *m, const struct man_node *n
+
+typedef        int     (*v_check)(CHKARGS);
+
+struct man_valid {
+       v_check  *pres;
+       v_check  *posts;
+};
+
+static int       check_bline(CHKARGS);
+static int       check_eq0(CHKARGS);
+static int       check_le1(CHKARGS);
+static int       check_ge2(CHKARGS);
+static int       check_le5(CHKARGS);
+static int       check_par(CHKARGS);
+static int       check_part(CHKARGS);
+static int       check_root(CHKARGS);
+static int       check_sec(CHKARGS);
+static int       check_text(CHKARGS);
+
+static v_check   posts_eq0[] = { check_eq0, NULL };
+static v_check   posts_ge2_le5[] = { check_ge2, check_le5, NULL };
+static v_check   posts_par[] = { check_par, NULL };
+static v_check   posts_part[] = { check_part, NULL };
+static v_check   posts_sec[] = { check_sec, NULL };
+static v_check   posts_sp[] = { check_le1, NULL };
+static v_check   pres_bline[] = { check_bline, NULL };
+
+static const struct man_valid man_valids[MAN_MAX] = {
+       { pres_bline, posts_eq0 }, /* br */
+       { pres_bline, posts_ge2_le5 }, /* TH */
+       { pres_bline, posts_sec }, /* SH */
+       { pres_bline, posts_sec }, /* SS */
+       { pres_bline, posts_par }, /* TP */
+       { pres_bline, posts_par }, /* LP */
+       { pres_bline, posts_par }, /* PP */
+       { pres_bline, posts_par }, /* P */
+       { pres_bline, posts_par }, /* IP */
+       { pres_bline, posts_par }, /* HP */
+       { NULL, NULL }, /* SM */
+       { NULL, NULL }, /* SB */
+       { NULL, NULL }, /* BI */
+       { NULL, NULL }, /* IB */
+       { NULL, NULL }, /* BR */
+       { NULL, NULL }, /* RB */
+       { NULL, NULL }, /* R */
+       { NULL, NULL }, /* B */
+       { NULL, NULL }, /* I */
+       { NULL, NULL }, /* IR */
+       { NULL, NULL }, /* RI */
+       { pres_bline, posts_eq0 }, /* na */
+       { NULL, NULL }, /* i */
+       { pres_bline, posts_sp }, /* sp */
+       { pres_bline, posts_eq0 }, /* nf */
+       { pres_bline, posts_eq0 }, /* fi */
+       { NULL, NULL }, /* r */
+       { NULL, NULL }, /* RE */
+       { NULL, posts_part }, /* RS */
+       { NULL, NULL }, /* DT */
+       { NULL, NULL }, /* UC */
+};
+
+
+int
+man_valid_pre(struct man *m, const struct man_node *n)
+{
+       v_check         *cp;
+
+       if (MAN_TEXT == n->type)
+               return(1);
+       if (MAN_ROOT == n->type)
+               return(1);
+
+       if (NULL == (cp = man_valids[n->tok].pres))
+               return(1);
+       for ( ; *cp; cp++)
+               if ( ! (*cp)(m, n))
+                       return(0);
+       return(1);
+}
+
+
+int
+man_valid_post(struct man *m)
+{
+       v_check         *cp;
+
+       if (MAN_VALID & m->last->flags)
+               return(1);
+       m->last->flags |= MAN_VALID;
+
+       switch (m->last->type) {
+       case (MAN_TEXT):
+               return(check_text(m, m->last));
+       case (MAN_ROOT):
+               return(check_root(m, m->last));
+       default:
+               break;
+       }
+
+       if (NULL == (cp = man_valids[m->last->tok].posts))
+               return(1);
+       for ( ; *cp; cp++)
+               if ( ! (*cp)(m, m->last))
+                       return(0);
+
+       return(1);
+}
+
+
+static int
+check_root(CHKARGS)
+{
+
+       if (MAN_BLINE & m->flags)
+               return(man_nwarn(m, n, WEXITSCOPE));
+       if (MAN_ELINE & m->flags)
+               return(man_nwarn(m, n, WEXITSCOPE));
+
+       m->flags &= ~MAN_BLINE;
+       m->flags &= ~MAN_ELINE;
+
+       if (NULL == m->first->child)
+               return(man_nerr(m, n, WNODATA));
+       if (NULL == m->meta.title)
+               return(man_nerr(m, n, WNOTITLE));
+
+       return(1);
+}
+
+
+static int
+check_text(CHKARGS)
+{
+       const char      *p;
+       int              pos, c;
+
+       assert(n->string);
+
+       for (p = n->string, pos = n->pos + 1; *p; p++, pos++) {
+               if ('\\' == *p) {
+                       c = mandoc_special(p);
+                       if (c) {
+                               p += c - 1;
+                               pos += c - 1;
+                               continue;
+                       }
+                       if ( ! (MAN_IGN_ESCAPE & m->pflags))
+                               return(man_perr(m, n->line, pos, WESCAPE));
+                       if ( ! man_pwarn(m, n->line, pos, WESCAPE))
+                               return(0);
+                       continue;
+               }
+
+               if ('\t' == *p || isprint((u_char)*p))
+                       continue;
+
+               if (MAN_IGN_CHARS & m->pflags)
+                       return(man_pwarn(m, n->line, pos, WNPRINT));
+               return(man_perr(m, n->line, pos, WNPRINT));
+       }
+
+       return(1);
+}
+
+
+#define        INEQ_DEFINE(x, ineq, name) \
+static int \
+check_##name(CHKARGS) \
+{ \
+       if (n->nchild ineq (x)) \
+               return(1); \
+       return(man_verr(m, n->line, n->pos, \
+                       "expected line arguments %s %d, have %d", \
+                       #ineq, (x), n->nchild)); \
+}
+
+INEQ_DEFINE(0, ==, eq0)
+INEQ_DEFINE(1, <=, le1)
+INEQ_DEFINE(2, >=, ge2)
+INEQ_DEFINE(5, <=, le5)
+
+
+static int
+check_sec(CHKARGS)
+{
+
+       if (MAN_BODY == n->type && 0 == n->nchild)
+               return(man_nwarn(m, n, WBODYARGS));
+       if (MAN_HEAD == n->type && 0 == n->nchild)
+               return(man_nerr(m, n, WHEADARGS));
+       return(1);
+}
+
+
+static int
+check_part(CHKARGS)
+{
+
+       if (MAN_BODY == n->type && 0 == n->nchild)
+               return(man_nwarn(m, n, WBODYARGS));
+       return(1);
+}
+
+
+static int
+check_par(CHKARGS)
+{
+
+       if (MAN_BODY == n->type)
+               switch (n->tok) {
+               case (MAN_IP):
+                       /* FALLTHROUGH */
+               case (MAN_HP):
+                       /* FALLTHROUGH */
+               case (MAN_TP):
+                       /* Body-less lists are ok. */
+                       break;
+               default:
+                       if (n->nchild)
+                               break;
+                       return(man_nwarn(m, n, WBODYARGS));
+               }
+       if (MAN_HEAD == n->type)
+               switch (n->tok) {
+               case (MAN_PP):
+                       /* FALLTHROUGH */
+               case (MAN_P):
+                       /* FALLTHROUGH */
+               case (MAN_LP):
+                       if (0 == n->nchild)
+                               break;
+                       return(man_nwarn(m, n, WNHEADARGS));
+               default:
+                       if (n->nchild)
+                               break;
+                       return(man_nwarn(m, n, WHEADARGS));
+               }
+
+       return(1);
+}
+
+
+static int
+check_bline(CHKARGS)
+{
+
+       assert( ! (MAN_ELINE & m->flags));
+       if (MAN_BLINE & m->flags)
+               return(man_nerr(m, n, WLNSCOPE));
+       return(1);
+}
diff --git a/usr.bin/mandoc/mandoc.1 b/usr.bin/mandoc/mandoc.1
new file mode 100644 (file)
index 0000000..ea744ea
--- /dev/null
@@ -0,0 +1,416 @@
+.\"    $Id: mandoc.1,v 1.17 2009/10/21 19:13:50 schwarze Exp $
+.\"
+.\" Copyright (c) 2009 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.
+.\"
+.Dd October 27, 2009
+.Dt MANDOC 1
+.Os
+.
+.
+.Sh NAME
+.Nm mandoc
+.Nd format and display UNIX manuals
+.
+.
+.Sh SYNOPSIS
+.Nm mandoc
+.Op Fl f Ns Ar option...
+.Op Fl m Ns Ar format
+.Op Fl o Ns Ar option...
+.Op Fl T Ns Ar output
+.Op Fl V
+.Op Fl W Ns Ar err...
+.Op Ar infile...
+.
+.
+.Sh DESCRIPTION
+The
+.Nm
+utility formats
+.Ux
+manual pages for display.  The arguments are as follows:
+.
+.Bl -tag -width Ds
+.It Fl f Ns Ar option...
+Comma-separated compiler options.  See
+.Sx Compiler Options
+for details.
+.
+.It Fl m Ns Ar format
+Input format.  See
+.Sx Input Formats
+for available formats.  Defaults to
+.Fl m Ns Ar andoc .
+.
+.It Fl o Ns Ar option...
+Comma-separated output options.  See
+.Sx Output Options
+for details.
+.
+.It Fl T Ns Ar output
+Output format.  See
+.Sx Output Formats
+for available formats.  Defaults to
+.Fl T Ns Ar ascii .
+.
+.It Fl V
+Print version and exit.
+.
+.It Fl W Ns Ar err...
+Comma-separated warning options.  Use
+.Fl W Ns Ar all
+to print warnings,
+.Fl W Ns Ar error
+for warnings to be considered errors and cause utility
+termination.  Multiple
+.Fl W
+arguments may be comma-separated, such as
+.Fl W Ns Ar error,all .
+.
+.It Ar infile...
+Read input from zero or more
+.Ar infile .
+If unspecified, reads from stdin.  If multiple files are specified,
+.Nm
+will halt with the first failed parse.
+.El
+.
+.Pp
+By default,
+.Nm
+reads
+.Xr mdoc 7
+or
+.Xr man 7
+text from stdin, implying
+.Fl m Ns Ar andoc ,
+and prints 78-column backspace-encoded output to stdout as if
+.Fl T Ns Ar ascii
+were provided.
+.
+.Pp
+.Ex -std mandoc
+.
+.
+.Ss Punctuation and Spacing
+If punctuation is set apart from words, such as in the phrase
+.Dq to be \&, or not to be ,
+it's processed by
+.Nm
+according to the following rules:  opening punctuation
+.Po
+.Sq \&( ,
+.Sq \&[ ,
+and
+.Sq \&{
+.Pc
+is not followed by a space; closing punctuation
+.Po
+.Sq \&. ,
+.Sq \&, ,
+.Sq \&; ,
+.Sq \&: ,
+.Sq \&? ,
+.Sq \&! ,
+.Sq \&) ,
+.Sq \&]
+and
+.Sq \&}
+.Pc
+is not preceded by whitespace.
+.
+.Pp
+If the input is
+.Xr mdoc 7 ,
+these rules are also applied to macro arguments when appropriate.
+.
+.Pp
+White-space, in non-literal (normal) mode, is stripped from input and
+replaced on output by a single space.  Thus, if you wish to preserve multiple
+spaces, they must be space-escaped or used in a literal display mode, e.g.,
+.Sq \&Bd \-literal
+in
+.Xr mdoc 7 .
+.
+.
+.Ss Input Formats
+The
+.Nm
+utility accepts
+.Xr mdoc 7
+and
+.Xr man 7
+input with
+.Fl m Ns Ar doc
+and
+.Fl m Ns Ar an ,
+respectively.  The
+.Xr mdoc 7
+format is
+.Em strongly
+recommended;
+.Xr man 7
+should only be used for legacy manuals.
+.
+.Pp
+A third option,
+.Fl m Ns Ar andoc ,
+which is also the default, determines encoding on-the-fly: if the first
+non-comment macro is
+.Sq \&Dd
+or
+.Sq \&Dt ,
+the
+.Xr mdoc 7
+parser is used; otherwise, the
+.Xr man 7
+parser is used.
+.
+.Pp
+If multiple
+files are specified with
+.Fl m Ns Ar andoc ,
+each has its file-type determined this way.  If multiple files are
+specified and
+.Fl m Ns Ar doc
+or
+.Fl m Ns Ar an
+is specified, then this format is used exclusively.
+.
+.
+.Ss Output Formats
+The
+.Nm
+utility accepts the following
+.Fl T
+arguments:
+.
+.Bl -tag -width Ds
+.It Fl T Ns Ar ascii
+Produce 7-bit ASCII output, backspace-encoded for bold and underline
+styles.  This is the default.
+.
+.It Fl T Ns Ar html
+Produce strict HTML-4.01 output, with a sane default style.
+.
+.It Fl T Ns Ar tree
+Produce an indented parse tree.
+.
+.It Fl T Ns Ar lint
+Parse only: produce no output.
+.El
+.
+.Pp
+If multiple input files are specified, these will be processed by the
+corresponding filter in-order.
+.
+.
+.Ss Compiler Options
+Default compiler behaviour may be overridden with the
+.Fl f
+flag.
+.
+.Bl -tag -width Ds
+.It Fl f Ns Ar ign-scope
+When rewinding the scope of a block macro, forces the compiler to ignore
+scope violations.  This can seriously mangle the resulting tree.
+.Pq mdoc only
+.
+.It Fl f Ns Ar no-ign-escape
+Don't ignore invalid escape sequences.
+.
+.It Fl f Ns Ar no-ign-macro
+Do not ignore unknown macros at the start of input lines.
+.
+.It Fl f Ns Ar no-ign-chars
+Do not ignore disallowed characters.
+.
+.It Fl f Ns Ar strict
+Implies
+.Fl f Ns Ar no-ign-escape ,
+.Fl f Ns Ar no-ign-macro
+and
+.Fl f Ns Ar no-ign-chars .
+.
+.It Fl f Ns Ar ign-errors
+Don't halt when encountering parse errors.  Useful with
+.Fl T Ns Ar lint
+over a large set of manuals passed on the command line.
+.El
+.
+.Ss Output Options
+For the time being, only
+.Fl T Ns Ar html
+is the only mode with output options:
+.Bl -tag -width Ds
+.It Fl o Ns Ar style=style.css
+The file
+.Ar style.css
+is used for an external style-sheet.  This must be a valid absolute or
+relative URI.
+.It Fl o Ns Ar includes=fmt
+The string
+.Ar fmt ,
+for example,
+.Ar ../src/%I.html ,
+is used as a template for linked header files (usually via the
+.Sq \&In
+macro).  Instances of
+.Sq \&%I
+are replaced with the include filename.  The default is not to present a
+hyperlink.
+.It Fl o Ns Ar man=fmt
+The string
+.Ar fmt ,
+for example,
+.Ar ../html%S/%N.%S.html ,
+is used as a template for linked manuals (usually via the
+.Sq \&Xr
+macro).  Instances of
+.Sq \&%N
+and
+.Sq %S
+are replaced with the linked manual's name and section, respectively.
+If no section is included, section 1 is assumed.  The default is not to
+present a hyperlink.
+.El
+.
+.Sh EXAMPLES
+To page manuals to the terminal:
+.
+.Pp
+.D1 % mandoc \-Wall,error \-fstrict mandoc.1 2>&1 | less
+.D1 % mandoc mandoc.1 mdoc.3 mdoc.7 | less
+.
+.Pp
+To produce HTML manuals with
+.Ar style.css
+as the style-sheet:
+.Pp
+.D1 % mandoc \-Thtml -ostyle=style.css mdoc.7 > mdoc.7.html
+.Pp
+To check over a large set of manuals:
+.
+.Pp
+.Dl % mandoc \-Tlint \-fign-errors `find /usr/src -name \e*\e.[1-9]`
+.
+.
+.Sh COMPATIBILITY
+This section summarises
+.Nm
+compatibility with
+.Xr groff 1 .
+Each input and output format is separately noted.
+.
+.
+.Ss ASCII output
+.Bl -bullet -compact
+.It
+The
+.Sq \e~
+special character doesn't produce expected behaviour in
+.Fl T Ns Ar ascii .
+.
+.It
+The
+.Sq \&Bd \-literal
+and
+.Sq \&Bd \-unfilled
+macros of
+.Xr mdoc 7
+in
+.Fl T Ns Ar ascii
+are synonyms, as are \-filled and \-ragged.
+.
+.It
+In
+.Xr groff 1 ,
+the
+.Sq \&Pa
+.Xr mdoc 7
+macro does not underline when scoped under an
+.Sq \&It
+in the FILES section.  This behaves correctly in
+.Nm .
+.
+.It
+A list or display following
+.Sq \&Ss
+.Xr mdoc 7
+macro in
+.Fl T Ns Ar ascii
+does not assert a prior vertical break, just as it doesn't with
+.Sq \&Sh .
+.
+.It
+The
+.Sq \&na
+.Xr man 7
+macro in
+.Fl T Ns Ar ascii
+has no effect.
+.
+.It
+Words aren't hyphenated.
+.
+.It
+In normal mode (not a literal block), blocks of spaces aren't preserved,
+so double spaces following sentence closure are reduced to a single space;
+.Xr groff 1
+retains spaces.
+.
+.It
+Sentences are unilaterally monospaced.
+.El
+.
+.Ss HTML output
+.Bl -bullet -compact
+.It
+The
+.Xr mdoc 7
+.Sq \&Bl \-hang
+and
+.Sq \&Bl \-tag
+list types render similarly (no break following overreached left-hand
+side) due to the expressive constraints of HTML.
+.
+.It
+The
+.Xr man 7
+.Sq IP
+and
+.Sq TP
+lists render similarly.
+.El
+.\" SECTION
+.Sh SEE ALSO
+.Xr mandoc_char 7 ,
+.Xr mdoc 7 ,
+.Xr man 7
+.
+.Sh AUTHORS
+The
+.Nm
+utility was written by
+.An Kristaps Dzonsons Aq kristaps@kth.se .
+.
+.Sh CAVEATS
+In
+.Fl T Ns Ar html ,
+the maximum size of an element attribute is determined by
+.Dv BUFSIZ ,
+which is usually 1024 bytes.  Be aware of this when setting long link
+formats with
+.Fl o Ns Ar man=fmt .
diff --git a/usr.bin/mandoc/mandoc.c b/usr.bin/mandoc/mandoc.c
new file mode 100644 (file)
index 0000000..98e983b
--- /dev/null
@@ -0,0 +1,104 @@
+/*     $Id: mandoc.c,v 1.3 2009/08/22 15:18:11 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009 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 "libmandoc.h"
+
+int
+mandoc_special(const char *p)
+{
+       int              c;
+
+       if ('\\' != *p++)
+               return(0);
+
+       switch (*p) {
+       case ('\\'):
+               /* FALLTHROUGH */
+       case ('\''):
+               /* FALLTHROUGH */
+       case ('`'):
+               /* FALLTHROUGH */
+       case ('q'):
+               /* FALLTHROUGH */
+       case ('-'):
+               /* FALLTHROUGH */
+       case ('~'):
+               /* FALLTHROUGH */
+       case ('^'):
+               /* FALLTHROUGH */
+       case ('%'):
+               /* FALLTHROUGH */
+       case ('0'):
+               /* FALLTHROUGH */
+       case (' '):
+               /* FALLTHROUGH */
+       case ('|'):
+               /* FALLTHROUGH */
+       case ('&'):
+               /* FALLTHROUGH */
+       case ('.'):
+               /* FALLTHROUGH */
+       case (':'):
+               /* FALLTHROUGH */
+       case ('c'):
+               return(2);
+       case ('e'):
+               return(2);
+       case ('f'):
+               if (0 == *++p || ! isgraph((u_char)*p))
+                       return(0);
+               return(3);
+       case ('*'):
+               if (0 == *++p || ! isgraph((u_char)*p))
+                       return(0);
+               switch (*p) {
+               case ('('):
+                       if (0 == *++p || ! isgraph((u_char)*p))
+                               return(0);
+                       return(4);
+               case ('['):
+                       for (c = 3, p++; *p && ']' != *p; p++, c++)
+                               if ( ! isgraph((u_char)*p))
+                                       break;
+                       return(*p == ']' ? c : 0);
+               default:
+                       break;
+               }
+               return(3);
+       case ('('):
+               if (0 == *++p || ! isgraph((u_char)*p))
+                       return(0);
+               if (0 == *++p || ! isgraph((u_char)*p))
+                       return(0);
+               return(4);
+       case ('['):
+               break;
+       default:
+               return(0);
+       }
+
+       for (c = 3, p++; *p && ']' != *p; p++, c++)
+               if ( ! isgraph((u_char)*p))
+                       break;
+
+       return(*p == ']' ? c : 0);
+}
diff --git a/usr.bin/mandoc/mandoc_char.7 b/usr.bin/mandoc/mandoc_char.7
new file mode 100644 (file)
index 0000000..94b5fc4
--- /dev/null
@@ -0,0 +1,616 @@
+.\"    $Id: mandoc_char.7,v 1.7 2009/10/21 19:13:50 schwarze Exp $
+.\"
+.\" Copyright (c) 2009 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.
+.\"
+.Dd $Mdocdate: October 19 2009 $
+.Dt MANDOC_CHAR 7
+.Os
+.
+.
+.Sh NAME
+.Nm mandoc_char
+.Nd mandoc special characters
+.
+.
+.Sh DESCRIPTION
+This documents the special characters and predefined strings accepted by
+.Xr mandoc 1
+to format
+.Xr mdoc 7
+and
+.Xr man 7
+documents.
+.
+.Pp
+Both
+.Xr mdoc 7
+and
+.Xr man 7
+encode special characters with
+.Sq \eX
+.Pq for a one-character escape ,
+.Sq \e(XX
+.Pq two-character ,
+and
+.Sq \e[N]
+.Pq N-character .
+One may generalise
+.Sq \e(XX
+as
+.Sq \e[XX]
+and
+.Sq \eX
+as
+.Sq \e[X] .
+Predefined strings are functionally similar to special characters, using
+.Sq \e*X
+.Pq for a one-character escape ,
+.Sq \e*(XX
+.Pq two-character ,
+and
+.Sq \e*[N]
+.Pq N-character .
+One may generalise
+.Sq \e*(XX
+as
+.Sq \e*[XX]
+and
+.Sq \e*X
+as
+.Sq \e*[X] .
+.
+.Pp
+Note that each output mode will have a different rendering of the
+characters.  It's guaranteed that each input symbol will correspond to a
+(more or less) meaningful output rendering, regardless the mode.
+.
+.Ss ASCII output
+Formatting documents with ASCII output results in a 7-bit ASCII
+approximation of zero or more characters, for example, the
+.Dq aleph
+character
+.Sq \e(Ah
+will render as
+.Sq N .
+Approximations are a best-effort, and naturally some clarity will be lost.
+.
+.Ss HTML output
+The HTML output mode uses decimal-encoded UTF-8 for sequences, for
+example, the
+.Dq aleph
+character
+.Sq \e(Ah
+will render as
+.Sq &#8501; .
+.
+.
+.Sh SPECIAL CHARACTERS
+These are the preferred input symbols for producing special characters.
+.
+.Pp
+Spacing:
+.Bl -column -compact -offset indent 10m 20m
+.It Em Input Ta Em Description
+.It \e~      Ta non-breaking, non-collapsing space
+.It \e       Ta breaking, non-collapsing n-width space
+.It \e^      Ta zero-width space
+.It \e%      Ta zero-width space
+.It \e&      Ta zero-width space
+.It \e|      Ta zero-width space
+.It \e0      Ta breaking, non-collapsing digit-width space
+.El
+.
+.Pp
+Lines:
+.Bl -column -compact -offset indent 10m 10m 10m
+.It Em Input Ta Em Rendered Ta Em Description
+.It \e(ba    Ta \(ba        Ta bar
+.It \e(br    Ta \(br        Ta box rule
+.It \e(ul    Ta \(ul        Ta underscore
+.It \e(rl    Ta \(rl        Ta overline
+.It \e(bb    Ta \(bb        Ta broken bar
+.It \e(sl    Ta \(sl        Ta forward slash
+.It \e(rs    Ta \(rs        Ta backward slash
+.El
+.
+.Pp
+Text markers:
+.Bl -column -compact -offset indent 10m 10m 10m
+.It Em Input Ta Em Rendered Ta Em Description
+.It \e(ci    Ta \(ci        Ta circle
+.It \e(bu    Ta \(bu        Ta bullet
+.It \e(dd    Ta \(dd        Ta double dagger
+.It \e(dg    Ta \(dg        Ta dagger
+.It \e(lz    Ta \(lz        Ta lozenge
+.It \e(sq    Ta \(sq        Ta white square
+.It \e(ps    Ta \(ps        Ta paragraph
+.It \e(sc    Ta \(sc        Ta section
+.It \e(lh    Ta \(lh        Ta left hand
+.It \e(rh    Ta \(rh        Ta right hand
+.It \e(at    Ta \(at        Ta at
+.It \e(sh    Ta \(sh        Ta hash (pound)
+.It \e(CR    Ta \(CR        Ta carriage return
+.It \e(OK    Ta \(OK        Ta check mark
+.El
+.
+.Pp
+Legal symbols:
+.Bl -column -compact -offset indent 10m 10m 10m
+.It Em Input Ta Em Rendered Ta Em Description
+.It \e(co    Ta \(co        Ta copyright
+.It \e(rg    Ta \(rg        Ta registered
+.It \e(tm    Ta \(tm        Ta trademarked
+.El
+.
+.Pp
+Punctuation:
+.Bl -column -compact -offset indent 10m 10m 10m
+.It Em Input Ta Em Rendered Ta Em Description
+.It \e(em    Ta \(em        Ta em-dash
+.It \e(en    Ta \(en        Ta en-dash
+.It \e(hy    Ta \(hy        Ta hyphen
+.It \e\e     Ta \\          Ta back-slash
+.It \ee      Ta \e          Ta back-slash
+.It \e.      Ta \.          Ta period
+.It \e(r!    Ta \(r!        Ta upside-down exclamation
+.It \e(r?    Ta \(r?        Ta upside-down question
+.El
+.
+.Pp
+Quotes:
+.Bl -column -compact -offset indent 10m 10m 10m
+.It Em Input Ta Em Rendered Ta Em Description
+.It \e(Bq    Ta \(Bq        Ta right low double-quote
+.It \e(bq    Ta \(bq        Ta right low single-quote
+.It \e(lq    Ta \(lq        Ta left double-quote
+.It \e(rq    Ta \(rq        Ta right double-quote
+.It \e(oq    Ta \(oq        Ta left single-quote
+.It \e(cq    Ta \(cq        Ta right single-quote
+.It \e(aq    Ta \(aq        Ta apostrophe quote (text)
+.It \e(dq    Ta \(dq        Ta double quote (text)
+.It \e(Fo    Ta \(Fo        Ta left guillemet
+.It \e(Fc    Ta \(Fc        Ta right guillemet
+.It \e(fo    Ta \(fo        Ta left single guillemet
+.It \e(fc    Ta \(fc        Ta right single guillemet
+.El
+.
+.Pp
+Brackets:
+.Bl -column -compact -offset indent 10m 10m 10m
+.It Em Input Ta Em Rendered Ta Em Description
+.It \e(lB    Ta \(lB        Ta left bracket
+.It \e(rB    Ta \(rB        Ta right bracket
+.It \e(lC    Ta \(lC        Ta left brace
+.It \e(rC    Ta \(rC        Ta right brace
+.It \e(la    Ta \(la        Ta left angle
+.It \e(ra    Ta \(ra        Ta right angle
+.It \e(bv    Ta \(bv        Ta brace extension
+.It \e[braceex] Ta \[braceex] Ta brace extension
+.It \e[bracketlefttp] Ta \[bracketlefttp] Ta top-left hooked bracket
+.It \e[bracketleftbp] Ta \[bracketleftbp] Ta bottom-left hooked bracket
+.It \e[bracketleftex] Ta \[bracketleftex] Ta left hooked bracket extension
+.It \e[bracketrighttp] Ta \[bracketrighttp] Ta top-right hooked bracket
+.It \e[bracketrightbp] Ta \[bracketrightbp] Ta bottom-right hooked bracket
+.It \e[bracketrightex] Ta \[bracketrightex] Ta right hooked bracket extension
+.It \e(lt    Ta \(lt        Ta top-left hooked brace
+.It \e[bracelefttp] Ta \[bracelefttp] Ta top-left hooked brace
+.It \e(lk    Ta \(lk        Ta mid-left hooked brace
+.It \e[braceleftmid] Ta \[braceleftmid] Ta mid-left hooked brace
+.It \e(lb    Ta \(lb        Ta bottom-left hooked brace
+.It \e[braceleftbp] Ta \[braceleftbp] Ta bottom-left hooked brace
+.It \e[braceleftex] Ta \[braceleftex] Ta left hooked brace extension
+.It \e(rt    Ta \(rt        Ta top-left hooked brace
+.It \e[bracerighttp] Ta \[bracerighttp] Ta top-right hooked brace
+.It \e(rk    Ta \(rk        Ta mid-right hooked brace
+.It \e[bracerightmid] Ta \[bracerightmid] Ta mid-right hooked brace
+.It \e(rb    Ta \(rb        Ta bottom-right hooked brace
+.It \e[bracerightbp] Ta \[bracerightbp] Ta bottom-right hooked brace
+.It \e[bracerightex] Ta \[bracerightex] Ta right hooked brace extension
+.It \e[parenlefttp] Ta \[parenlefttp] Ta top-left hooked parenthesis
+.It \e[parenleftbp] Ta \[parenleftbp] Ta bottom-left hooked parenthesis
+.It \e[parenleftex] Ta \[parenleftex] Ta left hooked parenthesis extension
+.It \e[parenrighttp] Ta \[parenrighttp] Ta top-right hooked parenthesis
+.It \e[parenrightbp] Ta \[parenrightbp] Ta bottom-right hooked parenthesis
+.It \e[parenrightex] Ta \[parenrightex] Ta right hooked parenthesis extension
+.El
+.
+.Pp
+Arrows:
+.Bl -column -compact -offset indent 10m 10m 10m
+.It Em Input Ta Em Rendered Ta Em Description
+.It \e(<-    Ta \(<-        Ta left arrow
+.It \e(->    Ta \(->        Ta right arrow
+.It \e(<>    Ta \(<>        Ta left-right arrow
+.It \e(da    Ta \(da        Ta down arrow
+.It \e(ua    Ta \(ua        Ta up arrow
+.It \e(va    Ta \(va        Ta up-down arrow
+.It \e(lA    Ta \(lA        Ta left double-arrow
+.It \e(rA    Ta \(rA        Ta right double-arrow
+.It \e(hA    Ta \(hA        Ta left-right double-arrow
+.It \e(uA    Ta \(uA        Ta up double-arrow
+.It \e(dA    Ta \(dA        Ta down double-arrow
+.It \e(vA    Ta \(vA        Ta up-down double-arrow
+.El
+.
+.Pp
+Logical:
+.Bl -column -compact -offset indent 10m 10m 10m
+.It Em Input Ta Em Rendered Ta Em Description
+.It \e(AN    Ta \(AN        Ta logical and
+.It \e(OR    Ta \(OR        Ta logical or
+.It \e(no    Ta \(no        Ta logical not
+.It \e[tno]  Ta \[tno]      Ta logical not (text)
+.It \e(te    Ta \(te        Ta existential quantifier
+.It \e(fa    Ta \(fa        Ta universal quantifier
+.It \e(st    Ta \(st        Ta such that
+.It \e(tf    Ta \(tf        Ta therefore
+.It \e(3d    Ta \(3d        Ta therefore
+.It \e(or    Ta \(or        Ta bitwise or
+.El
+.
+.Pp
+Mathematical:
+.Bl -column -compact -offset indent 10m 10m 10m
+.It Em Input Ta Em Rendered Ta Em Description
+.It \e(pl    Ta \(pl        Ta plus
+.It \e(mi    Ta \(mi        Ta minus
+.It \e-      Ta \-          Ta minus (text)
+.It \e(-+    Ta \(-+        Ta minus-plus
+.It \e(+-    Ta \(+-        Ta plus-minus
+.It \e[t+-]  Ta \[t+-]      Ta plus-minus (text)
+.It \e(pc    Ta \(pc        Ta centre-dot
+.It \e(mu    Ta \(mu        Ta multiply
+.It \e[tmu]  Ta \[tmu]      Ta multiply (text)
+.It \e(c*    Ta \(c*        Ta circle-multiply
+.It \e(c+    Ta \(c+        Ta circle-plus
+.It \e(di    Ta \(di        Ta divide
+.It \e[tdi]  Ta \[tdi]      Ta divide (text)
+.It \e(f/    Ta \(f/        Ta fraction
+.It \e(**    Ta \(**        Ta asterisk
+.It \e(<=    Ta \(<=        Ta less-than-equal
+.It \e(>=    Ta \(>=        Ta greater-than-equal
+.It \e(<<    Ta \(<<        Ta much less
+.It \e(>>    Ta \(>>        Ta much greater
+.It \e(eq    Ta \(eq        Ta equal
+.It \e(!=    Ta \(!=        Ta not equal
+.It \e(==    Ta \(==        Ta equivalent
+.It \e(ne    Ta \(ne        Ta not equivalent
+.It \e(=~    Ta \(=~        Ta congruent
+.It \e(-~    Ta \(-~        Ta asymptotically congruent
+.It \e(ap    Ta \(ap        Ta asymptotically similar
+.It \e(~~    Ta \(~~        Ta approximately similar
+.It \e(~=    Ta \(~=        Ta approximately equal
+.It \e(pt    Ta \(pt        Ta proportionate
+.It \e(es    Ta \(es        Ta empty set
+.It \e(mo    Ta \(mo        Ta element
+.It \e(nm    Ta \(nm        Ta not element
+.It \e(sb    Ta \(sb        Ta proper subset
+.It \e(nb    Ta \(nb        Ta not subset
+.It \e(sp    Ta \(sp        Ta proper superset
+.It \e(nc    Ta \(nc        Ta not superset
+.It \e(ib    Ta \(ib        Ta reflexive subset
+.It \e(ip    Ta \(ip        Ta reflexive superset
+.It \e(ca    Ta \(ca        Ta intersection
+.It \e(cu    Ta \(cu        Ta union
+.It \e(/_    Ta \(/_        Ta angle
+.It \e(pp    Ta \(pp        Ta perpendicular
+.It \e(is    Ta \(is        Ta integral
+.It \e[integral] Ta \[integral] Ta integral
+.It \e[sum]    Ta \[sum]   Ta summation
+.It \e[product] Ta \[product] Ta product
+.It \e[coproduct] Ta \[coproduct] Ta coproduct
+.It \e(gr    Ta \(gr        Ta gradient
+.It \e(sr    Ta \(sr        Ta square root
+.It \e[sqrt] Ta \[sqrt]     Ta square root
+.It \e(lc    Ta \(lc        Ta left-ceiling
+.It \e(rc    Ta \(rc        Ta right-ceiling
+.It \e(lf    Ta \(lf        Ta left-floor
+.It \e(rf    Ta \(rf        Ta right-floor
+.It \e(if    Ta \(if        Ta infinity
+.It \e(Ah    Ta \(Ah        Ta aleph
+.It \e(Im    Ta \(Im        Ta imaginary
+.It \e(Re    Ta \(Re        Ta real
+.It \e(pd    Ta \(pd        Ta partial differential
+.It \e(-h    Ta \(-h        Ta Planck constant over 2\(*p
+.El
+.
+.Pp
+Ligatures:
+.Bl -column -compact -offset indent 10m 10m 10m
+.It Em Input Ta Em Rendered Ta Em Description
+.It \e(ff    Ta \(ff        Ta ff ligature
+.It \e(fi    Ta \(fi        Ta fi ligature
+.It \e(fl    Ta \(fl        Ta fl ligature
+.It \e(Fi    Ta \(Fi        Ta ffi ligature
+.It \e(Fl    Ta \(Fl        Ta ffl ligature
+.It \e(AE    Ta \(AE        Ta AE
+.It \e(ae    Ta \(ae        Ta ae
+.It \e(OE    Ta \(OE        Ta OE
+.It \e(oe    Ta \(oe        Ta oe
+.It \e(ss    Ta \(ss        Ta German eszett
+.It \e(IJ    Ta \(IJ        Ta IJ ligature
+.It \e(ij    Ta \(ij        Ta ij ligature
+.El
+.
+.Pp
+Accents:
+.Bl -column -compact -offset indent 10m 10m 10m
+.It Em Input Ta Em Rendered Ta Em Description
+.It \e(a"    Ta \(a"        Ta Hungarian umlaut
+.It \e(a-    Ta \(a-        Ta macron
+.It \e(a.    Ta \(a.        Ta dotted
+.It \e(a^    Ta \(a^        Ta circumflex
+.It \e(aa    Ta \(aa        Ta acute
+.It \e'      Ta \'          Ta acute
+.It \e(ga    Ta \(ga        Ta grave
+.It \e`      Ta \`          Ta grave
+.It \e(ab    Ta \(ab        Ta breve
+.It \e(ac    Ta \(ac        Ta cedilla
+.It \e(ad    Ta \(ad        Ta dieresis
+.It \e(ah    Ta \(ah        Ta caron
+.It \e(ao    Ta \(ao        Ta ring
+.It \e(a~    Ta \(a~        Ta tilde
+.It \e(ho    Ta \(ho        Ta ogonek
+.It \e(ha    Ta \(ha        Ta hat (text)
+.It \e(ti    Ta \(ti        Ta tilde (text)
+.El
+.
+.Pp
+Accented letters:
+.Bl -column -compact -offset indent 10m 10m 10m
+.It Em Input Ta Em Rendered Ta Em Description
+.It \e('A    Ta \('A        Ta acute A
+.It \e('E    Ta \('E        Ta acute E
+.It \e('I    Ta \('I        Ta acute I
+.It \e('O    Ta \('O        Ta acute O
+.It \e('U    Ta \('U        Ta acute U
+.It \e('a    Ta \('a        Ta acute a
+.It \e('e    Ta \('e        Ta acute e
+.It \e('i    Ta \('i        Ta acute i
+.It \e('o    Ta \('o        Ta acute o
+.It \e('u    Ta \('u        Ta acute u
+.It \e(`A    Ta \(`A        Ta grave A
+.It \e(`E    Ta \(`E        Ta grave E
+.It \e(`I    Ta \(`I        Ta grave I
+.It \e(`O    Ta \(`O        Ta grave O
+.It \e(`U    Ta \(`U        Ta grave U
+.It \e(`a    Ta \(`a        Ta grave a
+.It \e(`e    Ta \(`e        Ta grave e
+.It \e(`i    Ta \(`i        Ta grave i
+.It \e(`o    Ta \(`i        Ta grave o
+.It \e(`u    Ta \(`u        Ta grave u
+.It \e(~A    Ta \(~A        Ta tilde A
+.It \e(~N    Ta \(~N        Ta tilde N
+.It \e(~O    Ta \(~O        Ta tilde O
+.It \e(~a    Ta \(~a        Ta tilde a
+.It \e(~n    Ta \(~n        Ta tilde n
+.It \e(~o    Ta \(~o        Ta tilde o
+.It \e(:A    Ta \(:A        Ta dieresis A
+.It \e(:E    Ta \(:E        Ta dieresis E
+.It \e(:I    Ta \(:I        Ta dieresis I
+.It \e(:O    Ta \(:O        Ta dieresis O
+.It \e(:U    Ta \(:U        Ta dieresis U
+.It \e(:a    Ta \(:a        Ta dieresis a
+.It \e(:e    Ta \(:e        Ta dieresis e
+.It \e(:i    Ta \(:i        Ta dieresis i
+.It \e(:o    Ta \(:o        Ta dieresis o
+.It \e(:u    Ta \(:u        Ta dieresis u
+.It \e(:y    Ta \(:y        Ta dieresis y
+.It \e(^A    Ta \(^A        Ta circumflex A
+.It \e(^E    Ta \(^E        Ta circumflex E
+.It \e(^I    Ta \(^I        Ta circumflex I
+.It \e(^O    Ta \(^O        Ta circumflex O
+.It \e(^U    Ta \(^U        Ta circumflex U
+.It \e(^a    Ta \(^a        Ta circumflex a
+.It \e(^e    Ta \(^e        Ta circumflex e
+.It \e(^i    Ta \(^i        Ta circumflex i
+.It \e(^o    Ta \(^o        Ta circumflex o
+.It \e(^u    Ta \(^u        Ta circumflex u
+.It \e(,C    Ta \(,C        Ta cedilla C
+.It \e(,c    Ta \(,c        Ta cedilla c
+.It \e(/L    Ta \(/L        Ta stroke L
+.It \e(/l    Ta \(/l        Ta stroke l
+.It \e(/O    Ta \(/O        Ta stroke O
+.It \e(/o    Ta \(/o        Ta stroke o
+.It \e(oA    Ta \(oA        Ta ring A
+.It \e(oa    Ta \(oa        Ta ring a
+.El
+.
+.Pp
+Special letters:
+.Bl -column -compact -offset indent 10m 10m 10m
+.It Em Input Ta Em Rendered Ta Em Description
+.It \e(-D    Ta \(-D        Ta Eth
+.It \e(Sd    Ta \(Sd        Ta eth
+.It \e(TP    Ta \(TP        Ta Thorn
+.It \e(Tp    Ta \(Tp        Ta thorn
+.It \e(.i    Ta \(.i        Ta dotless i
+.It \e(.j    Ta \(.j        Ta dotless j
+.El
+.
+.Pp
+Currency:
+.Bl -column -compact -offset indent 10m 10m 10m
+.It Em Input Ta Em Rendered Ta Em Description
+.It \e(Do    Ta \(Do        Ta dollar
+.It \e(ct    Ta \(ct        Ta cent
+.It \e(Eu    Ta \(Eu        Ta Euro symbol
+.It \e(eu    Ta \(eu        Ta Euro symbol
+.It \e(Ye    Ta \(Ye        Ta yen
+.It \e(Po    Ta \(Po        Ta pound
+.It \e(Cs    Ta \(Cs        Ta Scandinavian
+.It \e(Fn    Ta \(Fn        Ta florin
+.El
+.
+.Pp
+Units:
+.Bl -column -compact -offset indent 10m 10m 10m
+.It Em Input Ta Em Rendered Ta Em Description
+.It \e(de    Ta \(de        Ta degree
+.It \e(%0    Ta \(%0        Ta per-thousand
+.It \e(fm    Ta \(fm        Ta minute
+.It \e(sd    Ta \(sd        Ta second
+.It \e(mc    Ta \(mc        Ta micro
+.El
+.
+.Pp
+Greek letters:
+.Bl -column -compact -offset indent 10m 10m 10m
+.It Em Input Ta Em Rendered Ta Em Description
+.It \e(*A    Ta \(*A        Ta Alpha
+.It \e(*B    Ta \(*B        Ta Beta
+.It \e(*G    Ta \(*G        Ta Gamma
+.It \e(*D    Ta \(*D        Ta Delta
+.It \e(*E    Ta \(*E        Ta Epsilon
+.It \e(*Z    Ta \(*Z        Ta Zeta
+.It \e(*Y    Ta \(*Y        Ta Eta
+.It \e(*H    Ta \(*H        Ta Theta
+.It \e(*I    Ta \(*I        Ta Iota
+.It \e(*K    Ta \(*K        Ta Kappa
+.It \e(*L    Ta \(*L        Ta Lambda
+.It \e(*M    Ta \(*M        Ta Mu
+.It \e(*N    Ta \(*N        Ta Nu
+.It \e(*C    Ta \(*C        Ta Xi
+.It \e(*O    Ta \(*O        Ta Omicron
+.It \e(*P    Ta \(*P        Ta Pi
+.It \e(*R    Ta \(*R        Ta Rho
+.It \e(*S    Ta \(*S        Ta Sigma
+.It \e(*T    Ta \(*T        Ta Tau
+.It \e(*U    Ta \(*U        Ta Upsilon
+.It \e(*F    Ta \(*F        Ta Phi
+.It \e(*X    Ta \(*X        Ta Chi
+.It \e(*Q    Ta \(*Q        Ta Psi
+.It \e(*W    Ta \(*W        Ta Omega
+.It \e(*a    Ta \(*a        Ta alpha
+.It \e(*b    Ta \(*b        Ta beta
+.It \e(*g    Ta \(*g        Ta gamma
+.It \e(*d    Ta \(*d        Ta delta
+.It \e(*e    Ta \(*e        Ta epsilon
+.It \e(*z    Ta \(*z        Ta zeta
+.It \e(*y    Ta \(*y        Ta eta
+.It \e(*h    Ta \(*h        Ta theta
+.It \e(*i    Ta \(*i        Ta iota
+.It \e(*k    Ta \(*k        Ta kappa
+.It \e(*l    Ta \(*l        Ta lambda
+.It \e(*m    Ta \(*m        Ta mu
+.It \e(*n    Ta \(*n        Ta nu
+.It \e(*c    Ta \(*c        Ta xi
+.It \e(*o    Ta \(*o        Ta omicron
+.It \e(*p    Ta \(*p        Ta pi
+.It \e(*r    Ta \(*r        Ta rho
+.It \e(*s    Ta \(*s        Ta sigma
+.It \e(*t    Ta \(*t        Ta tau
+.It \e(*u    Ta \(*u        Ta upsilon
+.It \e(*f    Ta \(*f        Ta phi
+.It \e(*x    Ta \(*x        Ta chi
+.It \e(*q    Ta \(*q        Ta psi
+.It \e(*w    Ta \(*w        Ta omega
+.It \e(+h    Ta \(+h        Ta theta variant
+.It \e(+f    Ta \(+f        Ta phi variant
+.It \e(+p    Ta \(+p        Ta pi variant
+.It \e(+e    Ta \(+e        Ta epsilon variant
+.It \e(ts    Ta \(ts        Ta sigma terminal
+.El
+.
+.
+.Sh PREDEFINED STRINGS
+These are not recommended for use, as they differ across
+implementations:
+.
+.Pp
+.Bl -column -compact -offset indent 10m 10m 10m
+.It Em Input Ta Em Rendered Ta Em Description
+.It \e*(Ba   Ta \*(Ba       Ta vertical bar
+.It \e*(Ne   Ta \*(Ne       Ta not equal
+.It \e*(Ge   Ta \*(Ge       Ta greater-than-equal
+.It \e*(Le   Ta \*(Le       Ta less-than-equal
+.It \e*(Gt   Ta \*(Gt       Ta greater-than
+.It \e*(Lt   Ta \*(Lt       Ta less-than
+.It \e*(Pm   Ta \*(Pm       Ta plus-minus
+.It \e*(If   Ta \*(If       Ta infinity
+.It \e*(Pi   Ta \*(Pi       Ta pi
+.It \e*(Na   Ta \*(Na       Ta NaN
+.It \e*(Am   Ta \*(Am       Ta ampersand
+.It \e*R     Ta \*R         Ta restricted mark
+.It \e*(Tm   Ta \*(Tm       Ta trade mark
+.It \e*q     Ta \*q         Ta double-quote
+.It \e*(Rq   Ta \*(Rq       Ta right-double-quote
+.It \e*(Lq   Ta \*(Lq       Ta left-double-quote
+.It \e*(lp   Ta \*(lp       Ta right-parenthesis
+.It \e*(rp   Ta \*(rp       Ta left-parenthesis
+.It \e*(lq   Ta \*(lq       Ta left double-quote
+.It \e*(rq   Ta \*(rq       Ta right double-quote
+.It \e*(ua   Ta \*(ua       Ta up arrow
+.It \e*(va   Ta \*(va       Ta up-down arrow
+.It \e*(<=   Ta \*(<=       Ta less-than-equal
+.It \e*(>=   Ta \*(>=       Ta greater-than-equal
+.It \e*(aa   Ta \*(aa       Ta acute
+.It \e*(ga   Ta \*(ga       Ta grave
+.El
+.
+.
+.Sh COMPATIBILITY
+This section documents compatibility of
+.Nm
+with older or existing versions of
+.Xr groff 1 .
+.
+.Pp
+The following render differently in
+.Fl T Ns Ar ascii
+output mode:
+.Bd -ragged -offset indent
+\e(ss, \e(nm, \e(nb, \e(nc, \e(ib, \e(ip, \e(pp, \e[sum], \e[product],
+\e[coproduct], \e(gr, \e(-h, \e(a.
+.Ed
+.
+.Pp
+The following render differently in
+.Fl T Ns Ar html
+output mode:
+.Bd -ragged -offset indent
+\e(~=, \e(nb, \e(nc
+.Ed
+.
+.Pp
+Finally, the following have been omitted by being poorly documented or
+having no known representation:
+.Bd -ragged -offset indent
+\e[radicalex], \e[sqrtex], \e(ru
+.Ed
+.
+.
+.Sh SEE ALSO
+.Xr mandoc 1
+.
+.
+.Sh STANDARDS
+.Rs
+.%A The Unicode Consortium
+.%T The Unicode Standard: Worldwide Character Encoding, Version 5.2
+.%D 1991
+.Re
+.Rs
+.%A W3C
+.%T HTML 4.01 Specification
+.%D December, 1999
+.Re
+.
+.
+.Sh AUTHORS
+The
+.Nm
+utility was written by
+.An Kristaps Dzonsons Aq kristaps@kth.se .
diff --git a/usr.bin/mandoc/manuals.7 b/usr.bin/mandoc/manuals.7
new file mode 100644 (file)
index 0000000..b3f565b
--- /dev/null
@@ -0,0 +1,236 @@
+.\"    $Id: manuals.7,v 1.5 2009/08/22 16:32:22 schwarze Exp $
+.\"
+.\" Copyright (c) 2009 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.
+.\"
+.Dd $Mdocdate: July 26 2009 $
+.Dt MANUALS 7
+.Os
+.\" SECTION
+.Sh NAME
+.Nm Writing UNIX Documentation
+.Nd a guide to writing UNIX manuals
+.\" SECTION
+.Sh DESCRIPTION
+.Em A utility without good documentation is of no utility at all .
+.\" PARAGRAPH
+.Pp
+A system component's documentation describes the utility of that
+component, whether it's a device driver, an executable or, most
+importantly, a game.
+.Pp
+This document serves as a tutorial to writing
+.Ux
+documentation
+.Pq Dq manuals .
+.\" SECTION
+.Sh ENVIRONMENT
+First, copy over the manual template from
+.Pa /usr/share/misc/mdoc.template
+into your source directory.
+.Pp
+.Dl % cp /usr/share/misc/mdoc.template \.
+.Pp
+.Em \&Do not
+start afresh or by copying another manual unless you know exactly what
+you're doing!  If the template doesn't exist, bug your administrator.
+.\" SUBSECTION
+.Ss Section Numbering
+Find an appropriate section for your manual.  There may exist multiple
+manual names per section, so be specific:
+.Pp
+.\" LIST
+.Bl -tag -width "XXXXXXXXXXXX" -offset indent -compact
+.It Em Section
+.Em Description
+.It 1
+operator utilities
+.It 2
+system calls
+.It 3, 3p, 3f
+programming libraries (C, Perl, Fortran)
+.It 5
+file and wire protocol formats
+.It 6
+games
+.It 7
+tutorials, documents and papers
+.It 8
+administrator utilities
+.It 9
+in-kernel routines
+.El
+.Pp
+If your manual falls into multiple categories, choose the most
+widely-used or, better, re-consider the topic of your manual to be more
+specific.  You can list all manuals per section by invoking
+.Xr apropos 1 ,
+then provide the
+.Fl s
+flag to
+.Xr man 1
+to see the specific section manual (section 1, in this example):
+.\" DISPLAY
+.Bd -literal -offset indent
+% apropos myname
+myname (1) - utility description
+myname (3) - library description
+% man \-s 1 myname
+.Ed
+.\" SUBSECTION
+.Ss Naming
+Name your component.  Be terse, erring on the side of clarity.  Look for
+other manuals by that same name before committing:
+.Pp
+.Dl % apropos myname
+.Pp
+Manual files are named
+.Pa myname.mysection ,
+such as
+.Pa manuals.7
+for this document.  Rename the template file:
+.Pp
+.Dl % mv mdoc.template myname.mysection
+.\" SUBSECTION
+.Ss Development Tools
+While writing, make sure that your manual is correctly structured:
+.Pp
+.Dl % mandoc \-Tlint \-Wall \-fstrict name.1
+.Pp
+The quick-fix feature of
+.Xr vim 1
+is useful for checking over many manuals:
+.Bd -literal -offset indent
+% mandoc \-Wall \-fstrict \-Tlint \-fign-errors \e
+  ./path/to/manuals/* 2>&1 > /tmp/mandoc.errs
+% vim -q /tmp/mandoc.errs
+.Ed
+.Pp
+You may spell-check your work as follows:
+.Pp
+.Dl % deroff name.1 | spell
+.Pp
+If
+.Xr ispell 1
+is installed, it has a special mode for manuals:
+.Pp
+.Dl % ispell \-n name.1
+.Pp
+Use
+.Xr cvs 1
+or
+.Xr rcs 1
+to version-control your work.  If you wish the last check-in to effect
+your document's date, use the following RCS tag for the date macro:
+.Pp
+.Dl \&.Dd $Mdocdate: July 26 2009 $
+.\" SUBSECTION
+.Ss Viewing
+mdoc documents may be paged to your terminal with
+.Xr mandoc 1 .
+If you plan on distributing your work to systems without this tool,
+check it against
+.Xr groff 1 :
+.Bd -literal -offset indent
+% mandoc \-Wall name.1 2>&1 | less
+% groff -mandoc name.1 2>&1 | less
+.Ed
+.\" SUBSECTION
+.Ss Automation
+Consider adding your mdoc documents to
+.Xr make 1
+Makefiles in order to automatically check your input:
+.Bd -literal -offset indent
+\&.SUFFIXES: .1 .in
+
+\&.in.1:
+       mandoc -Wall,error -Tlint $<
+       cp -f $< $@
+.Ed
+.\" SUBSECTION
+.Ss Licensing
+Your manual must have a license.  It should be listed at the start of
+your document, just as in source code.
+.\" SECTION
+.Sh COMPOSITION
+Manuals should
+.Em always
+be written in the
+.Xr mdoc 7
+formatting language.
+.\" PARAGRAPH
+.Pp
+Open the template you've copied into
+.Pa myname.mysection
+and begin editing.
+.\" SUBSECTION
+.Ss Language
+.Bl -enum
+.It
+Use clear, concise language.  Favour simplicity.
+.It
+Write your manual in non-idiomatic English.  Don't worry about
+Commonwealth or American spellings \(em just correct ones.
+.It
+Spell-check your manual, keeping in mind short-letter terms (
+.Xr iwi 4
+vs.
+.Xr iwn 4 ) .
+.It
+If you absolutely must use special characters (diacritics, mathematical
+symbols and so on), use the escapes dictated in
+.Xr mdoc 7 .
+.El
+.\" SUBSECTION
+.Ss Style
+The structure of the mdoc language makes it very hard to have any
+particular format style.  Keep your lines under 72 characters in length.
+If you must have long option lines, use
+.Sq \&Oo/Oc .
+The same goes for function prototypes.
+.Em \&Do not
+use
+.Sq \&Xo/Xc .
+Find another way to structure your line.
+.\" SUBSECTION
+.Ss References
+Other components may be referenced with the
+.Sq \&Xr
+and
+.Sq \&Sx
+macros.  Make sure that these exist.  If you intend to distribute your
+manual, make sure
+.Sq \&Xr
+references are valid across systems (within reason).  If you cross-link with
+.Sq \&Sx ,
+make sure that the section reference exists.
+.\" SUBSECTION
+.Ss Citations
+Cite your work.  If your system references standards documents or other
+publications, please use the
+.Sq \&Rs/Re
+block macros.
+.\" SUBSECTION
+.Ss Formatting
+.Em Don't style your manual .
+Give it meaningful content.  The front-end will worry about formatting
+and style.
+.\" SECTION
+.Sh MAINTENANCE
+As your component changes and bugs are fixed, your manual may become out
+of date.  You may be tempted to use tools like Doxygen to automate the
+development of your manuals.  Don't.
+.Pp
+.Em Manuals are part of a system component :
+if you modify your code or specifications, modify the documentation.
diff --git a/usr.bin/mandoc/mdoc.3 b/usr.bin/mandoc/mdoc.3
new file mode 100644 (file)
index 0000000..2eb86c8
--- /dev/null
@@ -0,0 +1,336 @@
+.\"    $Id: mdoc.3,v 1.5 2009/10/20 10:15:04 schwarze Exp $
+.\"
+.\" Copyright (c) 2009 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.
+.\"
+.Dd $Mdocdate: August 9 2009 $
+.Dt MDOC 3
+.Os
+.\" SECTION
+.Sh NAME
+.Nm mdoc_alloc ,
+.Nm mdoc_parseln ,
+.Nm mdoc_endparse ,
+.Nm mdoc_node ,
+.Nm mdoc_meta ,
+.Nm mdoc_free ,
+.Nm mdoc_reset
+.Nd mdoc macro compiler library
+.\" SECTION
+.Sh SYNOPSIS
+.In mdoc.h
+.Vt extern const char * const * mdoc_macronames;
+.Vt extern const char * const * mdoc_argnames;
+.Ft "struct mdoc *"
+.Fn mdoc_alloc "void *data" "int pflags" "const struct mdoc_cb *cb"
+.Ft int
+.Fn mdoc_reset "struct mdoc *mdoc"
+.Ft void
+.Fn mdoc_free "struct mdoc *mdoc"
+.Ft int
+.Fn mdoc_parseln "struct mdoc *mdoc" "int line" "char *buf"
+.Ft "const struct mdoc_node *"
+.Fn mdoc_node "const struct mdoc *mdoc"
+.Ft "const struct mdoc_meta *"
+.Fn mdoc_meta "const struct mdoc *mdoc"
+.Ft int
+.Fn mdoc_endparse "struct mdoc *mdoc"
+.\" SECTION
+.Sh DESCRIPTION
+The
+.Nm mdoc
+library parses lines of
+.Xr mdoc 7
+input (and
+.Em only
+mdoc) into an abstract syntax tree (AST).
+.\" PARAGRAPH
+.Pp
+In general, applications initiate a parsing sequence with
+.Fn mdoc_alloc ,
+parse each line in a document with
+.Fn mdoc_parseln ,
+close the parsing session with
+.Fn mdoc_endparse ,
+operate over the syntax tree returned by
+.Fn mdoc_node
+and
+.Fn mdoc_meta ,
+then free all allocated memory with
+.Fn mdoc_free .
+The
+.Fn mdoc_reset
+function may be used in order to reset the parser for another input
+sequence.  See the
+.Sx EXAMPLES
+section for a full example.
+.\" PARAGRAPH
+.Pp
+This section further defines the
+.Sx Types ,
+.Sx Functions
+and
+.Sx Variables
+available to programmers.  Following that, the
+.Sx Abstract Syntax Tree
+section documents the output tree.
+.\" SUBSECTION
+.Ss Types
+Both functions (see
+.Sx Functions )
+and variables (see
+.Sx Variables )
+may use the following types:
+.Bl -ohang -offset "XXXX"
+.\" LIST-ITEM
+.It Vt struct mdoc
+An opaque type defined in
+.Pa mdoc.c .
+Its values are only used privately within the library.
+.\" LIST-ITEM
+.It Vt struct mdoc_cb
+A set of message callbacks defined in
+.Pa mdoc.h .
+.\" LIST-ITEM
+.It Vt struct mdoc_node
+A parsed node.  Defined in
+.Pa mdoc.h .
+See
+.Sx Abstract Syntax Tree
+for details.
+.El
+.\" SUBSECTION
+.Ss Functions
+Function descriptions follow:
+.Bl -ohang -offset "XXXX"
+.\" LIST-ITEM
+.It Fn mdoc_alloc
+Allocates a parsing structure.  The
+.Fa data
+pointer is passed to callbacks in
+.Fa cb ,
+which are documented further in the header file.
+The
+.Fa pflags
+arguments are defined in
+.Pa mdoc.h .
+Returns NULL on failure.  If non-NULL, the pointer must be freed with
+.Fn mdoc_free .
+.\" LIST-ITEM
+.It Fn mdoc_reset
+Reset the parser for another parse routine.  After its use,
+.Fn mdoc_parseln
+behaves as if invoked for the first time.  If it returns 0, memory could
+not be allocated.
+.\" LIST-ITEM
+.It Fn mdoc_free
+Free all resources of a parser.  The pointer is no longer valid after
+invocation.
+.\" LIST-ITEM
+.It Fn mdoc_parseln
+Parse a nil-terminated line of input.  This line should not contain the
+trailing newline.  Returns 0 on failure, 1 on success.  The input buffer
+.Fa buf
+is modified by this function.
+.\" LIST-ITEM
+.It Fn mdoc_endparse
+Signals that the parse is complete.  Note that if
+.Fn mdoc_endparse
+is called subsequent to
+.Fn mdoc_node ,
+the resulting tree is incomplete.  Returns 0 on failure, 1 on success.
+.\" LIST-ITEM
+.It Fn mdoc_node
+Returns the first node of the parse.  Note that if
+.Fn mdoc_parseln
+or
+.Fn mdoc_endparse
+return 0, the tree will be incomplete.
+.It Fn mdoc_meta
+Returns the document's parsed meta-data.  If this information has not
+yet been supplied or
+.Fn mdoc_parseln
+or
+.Fn mdoc_endparse
+return 0, the data will be incomplete.
+.El
+.\" SUBSECTION
+.Ss Variables
+The following variables are also defined:
+.Bl -ohang -offset "XXXX"
+.\" LIST-ITEM
+.It Va mdoc_macronames
+An array of string-ified token names.
+.\" LIST-ITEM
+.It Va mdoc_argnames
+An array of string-ified token argument names.
+.El
+.\" SUBSECTION
+.Ss Abstract Syntax Tree
+The
+.Nm
+functions produce an abstract syntax tree (AST) describing input in a
+regular form.  It may be reviewed at any time with
+.Fn mdoc_nodes ;
+however, if called before
+.Fn mdoc_endparse ,
+or after
+.Fn mdoc_endparse
+or
+.Fn mdoc_parseln
+fail, it may be incomplete.
+.\" PARAGRAPH
+.Pp
+This AST is governed by the ontological
+rules dictated in
+.Xr mdoc 7
+and derives its terminology accordingly.
+.Qq In-line
+elements described in
+.Xr mdoc 7
+are described simply as
+.Qq elements .
+.\" PARAGRAPH
+.Pp
+The AST is composed of
+.Vt struct mdoc_node
+nodes with block, head, body, element, root and text types as declared
+by the
+.Va type
+field.  Each node also provides its parse point (the
+.Va line ,
+.Va sec ,
+and
+.Va pos
+fields), its position in the tree (the
+.Va parent ,
+.Va child ,
+.Va next
+and
+.Va prev
+fields) and some type-specific data.
+.\" PARAGRAPH
+.Pp
+The tree itself is arranged according to the following normal form,
+where capitalised non-terminals represent nodes.
+.Pp
+.Bl -tag -width "ELEMENTXX" -compact -offset "XXXX"
+.\" LIST-ITEM
+.It ROOT
+\(<- mnode+
+.It mnode
+\(<- BLOCK | ELEMENT | TEXT
+.It BLOCK
+\(<- (HEAD [TEXT])+ [BODY [TEXT]] [TAIL [TEXT]]
+.It BLOCK
+\(<- BODY [TEXT] [TAIL [TEXT]]
+.It ELEMENT
+\(<- TEXT*
+.It HEAD
+\(<- mnode+
+.It BODY
+\(<- mnode+
+.It TAIL
+\(<- mnode+
+.It TEXT
+\(<- [[:alpha:]]*
+.El
+.\" PARAGRAPH
+.Pp
+Of note are the TEXT nodes following the HEAD, BODY and TAIL nodes of
+the BLOCK production.  These refer to punctuation marks.  Furthermore,
+although a TEXT node will generally have a non-zero-length string, in
+the specific case of
+.Sq \&.Bd \-literal ,
+an empty line will produce a zero-length string.
+.\" SECTION
+.Sh EXAMPLES
+The following example reads lines from stdin and parses them, operating
+on the finished parse tree with
+.Fn parsed .
+Note that, if the last line of the file isn't newline-terminated, this
+will truncate the file's last character (see
+.Xr fgetln 3 ) .
+Further, this example does not error-check nor free memory upon failure.
+.Bd -literal -offset "XXXX"
+struct mdoc *mdoc;
+const struct mdoc_node *node;
+char *buf;
+size_t len;
+int line;
+
+line = 1;
+mdoc = mdoc_alloc(NULL, 0, NULL);
+
+while ((buf = fgetln(fp, &len))) {
+       buf[len - 1] = '\\0';
+       if ( ! mdoc_parseln(mdoc, line, buf))
+               errx(1, "mdoc_parseln");
+       line++;
+}
+
+if ( ! mdoc_endparse(mdoc))
+       errx(1, "mdoc_endparse");
+if (NULL == (node = mdoc_node(mdoc)))
+       errx(1, "mdoc_node");
+
+parsed(mdoc, node);
+mdoc_free(mdoc);
+.Ed
+.\" SECTION
+.Sh SEE ALSO
+.Xr mandoc 1 ,
+.Xr mdoc 7
+.\" SECTION
+.Sh AUTHORS
+The
+.Nm
+utility was written by
+.An Kristaps Dzonsons Aq kristaps@kth.se .
+.\" SECTION
+.Sh CAVEATS
+.Bl -dash -compact
+.\" LIST-ITEM
+.It
+The
+.Sq \&.Xc
+and
+.Sq \&.Xo
+macros aren't handled when used to span lines for the
+.Sq \&.It
+macro.
+.\" LIST-ITEM
+.It
+The
+.Sq \&.Bsx
+macro family doesn't yet understand version arguments.
+.\" LIST-ITEM
+.It
+If not given a value, the \-offset argument to
+.Sq \&.Bd
+and
+.Sq \&.Bl
+should be the width of
+.Qq <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.
+.El
diff --git a/usr.bin/mandoc/mdoc.7 b/usr.bin/mandoc/mdoc.7
new file mode 100644 (file)
index 0000000..47ff812
--- /dev/null
@@ -0,0 +1,1775 @@
+.\"    $Id: mdoc.7,v 1.17 2009/10/21 19:13:50 schwarze Exp $